1 /*< ilib.js */ 2 /* 3 * ilib.js - define the ilib name space 4 * 5 * Copyright © 2012-2018, JEDLSoft 6 * 7 * Licensed under the Apache License, Version 2.0 (the "License"); 8 * you may not use this file except in compliance with the License. 9 * You may obtain a copy of the License at 10 * 11 * http://www.apache.org/licenses/LICENSE-2.0 12 * 13 * Unless required by applicable law or agreed to in writing, software 14 * distributed under the License is distributed on an "AS IS" BASIS, 15 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16 * 17 * See the License for the specific language governing permissions and 18 * limitations under the License. 19 */ 20 21 /** 22 * @namespace The global namespace that contains general ilib functions useful 23 * to all of ilib 24 * 25 * @version "13.3.0" 26 */ 27 var ilib = ilib || {}; 28 29 /** @private */ 30 ilib._ver = function() { 31 return "13.3.0" 32 ; 33 }; 34 35 /** 36 * Return the current version of ilib. 37 * 38 * @static 39 * @return {string} a version string for this instance of ilib 40 */ 41 ilib.getVersion = function () { 42 if (ilib._dyncode) { 43 try { 44 var pkg; 45 pkg = require("../package.json"); 46 return pkg.version; 47 } catch (e) { 48 // ignore 49 } 50 } 51 return ilib._ver() || "13.0"; 52 }; 53 54 /** 55 * Place where resources and such are eventually assigned. 56 */ 57 ilib.data = { 58 /** @type {{ccc:Object.<string,number>,nfd:Object.<string,string>,nfc:Object.<string,string>,nfkd:Object.<string,string>,nfkc:Object.<string,string>}} */ 59 norm: { 60 ccc: {}, 61 nfd: {}, 62 nfc: {}, 63 nfkd: {}, 64 nfkc: {} 65 }, 66 zoneinfo: { 67 "Etc/UTC":{"o":"0:0","f":"UTC"}, 68 "local":{"f":"local"} 69 }, 70 /** @type {Object.<string,{to:Object.<string,string>,from:Object.<string,number>}>} */ charmaps: {}, 71 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype: null, 72 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_c: null, 73 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_l: null, 74 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_m: null, 75 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_p: null, 76 /** @type {null|Object.<string,Array.<Array.<number>>>} */ ctype_z: null, 77 /** @type {null|Object.<string,Array.<Array.<number>>>} */ scriptToRange: null, 78 /** @type {null|Object.<string,string|Object.<string|Object.<string,string>>>} */ dateformats: null, 79 /** @type {null|Array.<string>} */ timezones: [], 80 cache: {} 81 }; 82 83 /* 84 if (typeof(window) !== 'undefined') { 85 window["ilib"] = ilib; 86 } 87 */ 88 89 // export ilib for use as a module in nodejs 90 if (typeof(module) !== 'undefined') { 91 module.exports = ilib; 92 module.exports.ilib = ilib; // for backwards compatibility with older versions of ilib 93 } 94 95 /** 96 * Sets the pseudo locale. Pseudolocalization (or pseudo-localization) is used for testing 97 * internationalization aspects of software. Instead of translating the text of the software 98 * into a foreign language, as in the process of localization, the textual elements of an application 99 * are replaced with an altered version of the original language.These specific alterations make 100 * the original words appear readable, but include the most problematic characteristics of 101 * the world's languages: varying length of text or characters, language direction, and so on. 102 * Regular Latin pseudo locale: eu-ES and RTL pseudo locale: ps-AF 103 * 104 * @param {string|undefined|null} localename the locale specifier for the pseudo locale 105 */ 106 ilib.setAsPseudoLocale = function (localename) { 107 if (localename) { 108 ilib.pseudoLocales.push(localename) 109 } 110 }; 111 112 /** 113 * Reset the list of pseudo locales back to the default single locale of zxx-XX. 114 * @static 115 */ 116 ilib.clearPseudoLocales = function() { 117 ilib.pseudoLocales = [ 118 "zxx-XX", 119 "zxx-Cyrl-XX", 120 "zxx-Hans-XX", 121 "zxx-Hebr-XX" 122 ]; 123 }; 124 125 ilib.clearPseudoLocales(); 126 127 /** 128 * Return the name of the platform 129 * @private 130 * @static 131 * @return {string} string naming the platform 132 */ 133 ilib._getPlatform = function () { 134 if (!ilib._platform) { 135 try { 136 if (typeof(java.lang.Object) !== 'undefined') { 137 ilib._platform = (typeof(process) !== 'undefined') ? "trireme" : "rhino"; 138 return ilib._platform; 139 } 140 } catch (e) {} 141 142 if (typeof(process) !== 'undefined' && process.versions && process.versions.node && typeof(module) !== 'undefined') { 143 ilib._platform = "nodejs"; 144 } else if (typeof(Qt) !== 'undefined') { 145 ilib._platform = "qt"; 146 } else if (typeof(window) !== 'undefined') { 147 ilib._platform = (typeof(PalmSystem) !== 'undefined') ? "webos" : "browser"; 148 } else { 149 ilib._platform = "unknown"; 150 } 151 } 152 return ilib._platform; 153 }; 154 155 /** 156 * If this ilib is running in a browser, return the name of that browser. 157 * @private 158 * @static 159 * @return {string|undefined} the name of the browser that this is running in ("firefox", "chrome", "ie", 160 * "safari", or "opera"), or undefined if this is not running in a browser or if 161 * the browser name could not be determined 162 */ 163 ilib._getBrowser = function () { 164 var browser = undefined; 165 if (ilib._getPlatform() === "browser") { 166 if (navigator && navigator.userAgent) { 167 if (navigator.userAgent.indexOf("Firefox") > -1) { 168 browser = "firefox"; 169 } 170 if (navigator.userAgent.search(/Opera|OPR/) > -1 ) { 171 browser = "opera"; 172 } 173 if (navigator.userAgent.indexOf("Chrome") > -1) { 174 browser = "chrome"; 175 } 176 if (navigator.userAgent.indexOf(" .NET") > -1) { 177 browser = "ie"; 178 } 179 if (navigator.userAgent.indexOf("Safari") > -1) { 180 // chrome also has the string Safari in its userAgent, but the chrome case is 181 // already taken care of above 182 browser = "safari"; 183 } 184 if (navigator.userAgent.indexOf("Edge") > -1) { 185 browser = "Edge"; 186 } 187 if (navigator.userAgent.search(/iPad|iPhone|iPod/) > -1) { 188 // Due to constraints of the iOS platform, 189 // all browser must be built on top of the WebKit rendering engine 190 browser = "iOS"; 191 } 192 } 193 } 194 return browser; 195 }; 196 197 /** 198 * Return the value of a global variable given its name in a way that works 199 * correctly for the current platform. 200 * @private 201 * @static 202 * @param {string} name the name of the variable to return 203 * @return {*} the global variable, or undefined if it does not exist 204 */ 205 ilib._global = function(name) { 206 switch (ilib._getPlatform()) { 207 case "rhino": 208 var top = (function() { 209 return (typeof global === 'object') ? global : this; 210 })(); 211 break; 212 case "nodejs": 213 case "trireme": 214 top = typeof(global) !== 'undefined' ? global : this; 215 //console.log("ilib._global: top is " + (typeof(global) !== 'undefined' ? "global" : "this")); 216 break; 217 case "qt": 218 return undefined; 219 default: 220 top = window; 221 break; 222 } 223 try { 224 return top[name]; 225 } catch (e) { 226 return undefined; 227 } 228 }; 229 230 /** 231 * Return true if the global variable is defined on this platform. 232 * @private 233 * @static 234 * @param {string} name the name of the variable to check 235 * @return {boolean} true if the global variable is defined on this platform, false otherwise 236 */ 237 ilib._isGlobal = function(name) { 238 return typeof(ilib._global(name)) !== 'undefined'; 239 }; 240 241 /** 242 * Clear the file load cache. This is mainly used by the unit tests, 243 * but could be used by regular callers if you want to free up memory 244 * for garbage collecting. 245 */ 246 ilib.clearCache = function() { 247 ilib.data.cache = {}; 248 }; 249 250 /** 251 * Sets the default locale for all of ilib. This locale will be used 252 * when no explicit locale is passed to any ilib class. If the default 253 * locale is not set, ilib will attempt to use the locale of the 254 * environment it is running in, if it can find that. If not, it will 255 * default to the locale "en-US". If a type of parameter is string, 256 * ilib will take only well-formed BCP-47 tag <p> 257 * 258 * 259 * @static 260 * @param {string|undefined|null} spec the locale specifier for the default locale 261 */ 262 ilib.setLocale = function (spec) { 263 if (typeof(spec) === 'string' || !spec) { 264 ilib.locale = spec; 265 } 266 // else ignore other data types, as we don't have the dependencies 267 // to look into them to find a locale 268 }; 269 270 /** 271 * Return the default locale for all of ilib if one has been set. This 272 * locale will be used when no explicit locale is passed to any ilib 273 * class. If the default 274 * locale is not set, ilib will attempt to use the locale of the 275 * environment it is running in, if it can find that. If not, it will 276 * default to the locale "en-US".<p> 277 * 278 * 279 * @static 280 * @return {string} the locale specifier for the default locale 281 */ 282 ilib.getLocale = function () { 283 if (typeof(ilib.locale) !== 'string') { 284 var lang, plat = ilib._getPlatform(); 285 switch (plat) { 286 case 'browser': 287 // running in a browser 288 if(typeof(navigator.language) !== 'undefined') { 289 ilib.locale = navigator.language.substring(0,3) + navigator.language.substring(3,5).toUpperCase(); // FF/Opera/Chrome/Webkit 290 } 291 if (!ilib.locale) { 292 // IE on Windows 293 lang = typeof(navigator.browserLanguage) !== 'undefined' ? 294 navigator.browserLanguage : 295 (typeof(navigator.userLanguage) !== 'undefined' ? 296 navigator.userLanguage : 297 (typeof(navigator.systemLanguage) !== 'undefined' ? 298 navigator.systemLanguage : 299 undefined)); 300 if (typeof(lang) !== 'undefined' && lang) { 301 // for some reason, MS uses lower case region tags 302 ilib.locale = lang.substring(0,3) + lang.substring(3,5).toUpperCase(); 303 } 304 } 305 break; 306 case 'webos': 307 // webOS 308 if (typeof(PalmSystem.locales) !== 'undefined' && 309 typeof(PalmSystem.locales.UI) != 'undefined' && 310 PalmSystem.locales.UI.length > 0) { 311 ilib.locale = PalmSystem.locales.UI; 312 } else if (typeof(PalmSystem.locale) !== 'undefined') { 313 ilib.locale = PalmSystem.locale; 314 } 315 break; 316 case 'rhino': 317 if (typeof(environment) !== 'undefined' && environment.user && typeof(environment.user.language) === 'string' && environment.user.language.length > 0) { 318 // running under plain rhino 319 ilib.locale = environment.user.language; 320 if (typeof(environment.user.country) === 'string' && environment.user.country.length > 0) { 321 ilib.locale += '-' + environment.user.country; 322 } 323 } 324 break; 325 case "trireme": 326 // under trireme on rhino emulating nodejs 327 lang = process.env.LANG || process.env.LANGUAGE || process.env.LC_ALL; 328 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 329 // where language and region are the correct ISO codes separated by 330 // an underscore. This translate it back to the BCP-47 form. 331 if (lang && typeof(lang) !== 'undefined') { 332 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 333 } 334 break; 335 case 'nodejs': 336 // running under nodejs 337 lang = process.env.LANG || process.env.LC_ALL; 338 // the LANG variable on unix is in the form "lang_REGION.CHARSET" 339 // where language and region are the correct ISO codes separated by 340 // an underscore. This translate it back to the BCP-47 form. 341 if (lang && typeof(lang) !== 'undefined') { 342 ilib.locale = lang.substring(0,2).toLowerCase() + '-' + lang.substring(3,5).toUpperCase(); 343 } 344 break; 345 case 'qt': 346 // running in the Javascript engine under Qt/QML 347 var locobj = Qt.locale(); 348 lang = locobj.name && locobj.name.replace("_", "-") || "en-US"; 349 break; 350 } 351 ilib.locale = typeof(ilib.locale) === 'string' && ilib.locale ? ilib.locale : 'en-US'; 352 if (ilib.locale === "en") { 353 ilib.locale = "en-US"; // hack to get various platforms working correctly 354 } 355 } 356 return ilib.locale; 357 }; 358 359 /** 360 * Sets the default time zone for all of ilib. This time zone will be used when 361 * no explicit time zone is passed to any ilib class. If the default time zone 362 * is not set, ilib will attempt to use the time zone of the 363 * environment it is running in, if it can find that. If not, it will 364 * default to the the UTC zone "Etc/UTC".<p> 365 * 366 * 367 * @static 368 * @param {string} tz the name of the time zone to set as the default time zone 369 */ 370 ilib.setTimeZone = function (tz) { 371 ilib.tz = tz || ilib.tz; 372 }; 373 374 /** 375 * Return the default time zone for all of ilib if one has been set. This 376 * time zone will be used when no explicit time zone is passed to any ilib 377 * class. If the default time zone 378 * is not set, ilib will attempt to use the locale of the 379 * environment it is running in, if it can find that. If not, it will 380 * default to the the zone "local".<p> 381 * 382 * 383 * @static 384 * @return {string} the default time zone for ilib 385 */ 386 ilib.getTimeZone = function() { 387 if (typeof(ilib.tz) === 'undefined') { 388 if (typeof(Intl) !== 'undefined' && typeof(Intl.DateTimeFormat) !== 'undefined') { 389 var ro = new Intl.DateTimeFormat().resolvedOptions(); 390 ilib.tz = ro && ro.timeZone; 391 } 392 393 if (!ilib.tz) { 394 if (typeof(navigator) !== 'undefined' && typeof(navigator.timezone) !== 'undefined') { 395 // running in a browser 396 if (navigator.timezone.length > 0) { 397 ilib.tz = navigator.timezone; 398 } 399 } else if (typeof(PalmSystem) !== 'undefined' && typeof(PalmSystem.timezone) !== 'undefined') { 400 // running in webkit on webOS 401 if (PalmSystem.timezone.length > 0) { 402 ilib.tz = PalmSystem.timezone; 403 } 404 } else if (typeof(environment) !== 'undefined' && typeof(environment.user) !== 'undefined') { 405 // running under rhino 406 if (typeof(environment.user.timezone) !== 'undefined' && environment.user.timezone.length > 0) { 407 ilib.tz = environment.user.timezone; 408 } 409 } else if (typeof(process) !== 'undefined' && typeof(process.env) !== 'undefined') { 410 // running in nodejs 411 if (process.env.TZ && typeof(process.env.TZ) !== "undefined") { 412 ilib.tz = process.env.TZ; 413 } 414 } 415 416 ilib.tz = ilib.tz || "local"; 417 } 418 } 419 420 return ilib.tz; 421 }; 422 423 /** 424 * @class 425 * Defines the interface for the loader class for ilib. The main method of the 426 * loader object is loadFiles(), which loads a set of requested locale data files 427 * from where-ever it is stored. 428 * @interface 429 */ 430 ilib.Loader = function() {}; 431 432 /** 433 * Load a set of files from where-ever it is stored.<p> 434 * 435 * This is the main function define a callback function for loading missing locale 436 * data or resources. 437 * If this copy of ilib is assembled without including the required locale data 438 * or resources, then that data can be lazy loaded dynamically when it is 439 * needed by calling this method. Each ilib class will first 440 * check for the existence of data under ilib.data, and if it is not there, 441 * it will attempt to load it by calling this method of the laoder, and then place 442 * it there.<p> 443 * 444 * Suggested implementations of this method might load files 445 * directly from disk under nodejs or rhino, or within web pages, to load 446 * files from the server with XHR calls.<p> 447 * 448 * The first parameter to this method, paths, is an array of relative paths within 449 * the ilib dir structure for the 450 * requested data. These paths will already have the locale spec integrated 451 * into them, so no further tweaking needs to happen to load the data. Simply 452 * load the named files. The second 453 * parameter tells the loader whether to load the files synchronously or asynchronously. 454 * If the sync parameters is false, then the onLoad function must also be specified. 455 * The third parameter gives extra parameters to the loader passed from the calling 456 * code. This may contain any property/value pairs. The last parameter, callback, 457 * is a callback function to call when all of the data is finishing loading. Make 458 * sure to call the callback with the context of "this" so that the caller has their 459 * context back again.<p> 460 * 461 * The loader function must be able to operate either synchronously or asychronously. 462 * If the loader function is called with an undefined callback function, it is 463 * expected to load the data synchronously, convert it to javascript 464 * objects, and return the array of json objects as the return value of the 465 * function. If the loader 466 * function is called with a callback function, it may load the data 467 * synchronously or asynchronously (doesn't matter which) as long as it calls 468 * the callback function with the data converted to a javascript objects 469 * when it becomes available. If a particular file could not be loaded, the 470 * loader function should put undefined into the corresponding entry in the 471 * results array. 472 * Note that it is important that all the data is loaded before the callback 473 * is called.<p> 474 * 475 * An example implementation for nodejs might be: 476 * 477 * <pre> 478 * * 479 * var myLoader = function() {}; 480 * myLoader.prototype = new Loader(); 481 * myLoader.prototype.constructor = myLoader; 482 * myLoader.prototype.loadFiles = function(paths, sync, params, callback) { 483 * if (sync) { 484 * var ret = []; 485 * // synchronous load -- just return the result 486 * paths.forEach(function (path) { 487 * var json = fs.readFileSync(path, "utf-8"); 488 * ret.push(json ? JSON.parse(json) : undefined); 489 * }); 490 * 491 * return ret; 492 * } 493 * this.callback = callback; 494 * 495 * // asynchronous 496 * this.results = []; 497 * this._loadFilesAsync(paths); 498 * } 499 * myLoader.prototype._loadFilesAsync = function (paths) { 500 * if (paths.length > 0) { 501 * var file = paths.shift(); 502 * fs.readFile(file, "utf-8", function(err, json) { 503 * this.results.push(err ? undefined : JSON.parse(json)); 504 * // call self recursively so that the callback is only called at the end 505 * // when all the files are loaded sequentially 506 * if (paths.length > 0) { 507 * this._loadFilesAsync(paths); 508 * } else { 509 * this.callback(this.results); 510 * } 511 * }); 512 * } 513 * } 514 * 515 * // bind to "this" so that "this" is relative to your own instance 516 * ilib.setLoaderCallback(new myLoader()); 517 * </pre> 518 519 * @param {Array.<string>} paths An array of paths to load from wherever the files are stored 520 * @param {Boolean} sync if true, load the files synchronously, and false means asynchronously 521 * @param {Object} params an object with any extra parameters for the loader. These can be 522 * anything. The caller of the ilib class passes these parameters in. Presumably, the code that 523 * calls ilib and the code that provides the loader are together and can have a private 524 * agreement between them about what the parameters should contain. 525 * @param {function(Object)} callback function to call when the files are all loaded. The 526 * parameter of the callback function is the contents of the files. 527 */ 528 ilib.Loader.prototype.loadFiles = function (paths, sync, params, callback) {}; 529 530 /** 531 * Return all files available for loading using this loader instance. 532 * This method returns an object where the properties are the paths to 533 * directories where files are loaded from and the values are an array 534 * of strings containing the relative paths under the directory of each 535 * file that can be loaded.<p> 536 * 537 * Example: 538 * <pre> 539 * { 540 * "/usr/share/javascript/ilib/locale": [ 541 * "dateformats.json", 542 * "aa/dateformats.json", 543 * "af/dateformats.json", 544 * "agq/dateformats.json", 545 * "ak/dateformats.json", 546 * ... 547 * "zxx/dateformats.json" 548 * ] 549 * } 550 * </pre> 551 * @returns {Object} a hash containing directory names and 552 * paths to file that can be loaded by this loader 553 */ 554 ilib.Loader.prototype.listAvailableFiles = function() {}; 555 556 /** 557 * Return true if the file in the named path is available for loading using 558 * this loader. The path may be given as an absolute path, in which case 559 * only that file is checked, or as a relative path, in which case, the 560 * relative path may appear underneath any of the directories that the loader 561 * knows about. 562 * @returns {boolean} true if the file in the named path is available for loading, and 563 * false otherwise 564 */ 565 ilib.Loader.prototype.isAvailable = function(path) {}; 566 567 /** 568 * Set the custom loader used to load ilib's locale data in your environment. 569 * The instance passed in must implement the Loader interface. See the 570 * Loader class documentation for more information about loaders. 571 * 572 * @static 573 * @param {ilib.Loader} loader class to call to access the requested data. 574 * @return {boolean} true if the loader was installed correctly, or false 575 * if not 576 */ 577 ilib.setLoaderCallback = function(loader) { 578 // only a basic check 579 if ((typeof(loader) === 'object' && typeof(loader.loadFiles) === 'function') || 580 typeof(loader) === 'function' || typeof(loader) === 'undefined') { 581 //console.log("setting callback loader to " + (loader ? loader.name : "undefined")); 582 ilib._load = loader; 583 return true; 584 } 585 return false; 586 }; 587 588 /** 589 * Return the custom Loader instance currently in use with this instance 590 * of ilib. If there is no loader, this method returns undefined. 591 * 592 * @protected 593 * @static 594 * @return {ilib.Loader|undefined} the loader instance currently in use, or 595 * undefined if there is no such loader 596 */ 597 ilib.getLoader = function() { 598 return ilib._load; 599 }; 600 601 /** 602 * Test whether an object is an javascript array. 603 * 604 * @static 605 * @param {*} object The object to test 606 * @return {boolean} return true if the object is an array 607 * and false otherwise 608 */ 609 ilib.isArray = function(object) { 610 if (typeof(object) === 'object') { 611 return Object.prototype.toString.call(object) === '[object Array]'; 612 } 613 return false; 614 }; 615 616 /** 617 * Extend object1 by mixing in everything from object2 into it. The objects 618 * are deeply extended, meaning that this method recursively descends the 619 * tree in the objects and mixes them in at each level. Arrays are extended 620 * by concatenating the elements of object2 onto those of object1. 621 * 622 * @static 623 * @param {Object} object1 the target object to extend 624 * @param {Object=} object2 the object to mix in to object1 625 * @return {Object} returns object1 626 */ 627 ilib.extend = function (object1, object2) { 628 var prop = undefined; 629 if (object2) { 630 for (prop in object2) { 631 // don't extend object with undefined or functions 632 if (prop && typeof(object2[prop]) !== 'undefined' && typeof(object2[prop]) !== "function") { 633 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 634 //console.log("Merging array prop " + prop); 635 object1[prop] = object1[prop].concat(object2[prop]); 636 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 637 //console.log("Merging object prop " + prop); 638 if (prop !== "ilib") { 639 object1[prop] = ilib.extend(object1[prop], object2[prop]); 640 } 641 } else { 642 //console.log("Copying prop " + prop); 643 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 644 object1[prop] = object2[prop]; 645 } 646 } 647 } 648 } 649 return object1; 650 }; 651 652 ilib.extend2 = function (object1, object2) { 653 var prop = undefined; 654 if (object2) { 655 for (prop in object2) { 656 // don't extend object with undefined or functions 657 if (prop && typeof(object2[prop]) !== 'undefined') { 658 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 659 //console.log("Merging array prop " + prop); 660 object1[prop] = object1[prop].concat(object2[prop]); 661 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 662 //console.log("Merging object prop " + prop); 663 if (prop !== "ilib") { 664 object1[prop] = ilib.extend2(object1[prop], object2[prop]); 665 } 666 } else { 667 //console.log("Copying prop " + prop); 668 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 669 object1[prop] = object2[prop]; 670 } 671 } 672 } 673 } 674 return object1; 675 }; 676 677 /** 678 * If Function.prototype.bind does not exist in this JS engine, this 679 * function reimplements it in terms of older JS functions. 680 * bind() doesn't exist in many older browsers. 681 * 682 * @static 683 * @param {Object} scope object that the method should operate on 684 * @param {function(...)} method method to call 685 * @return {function(...)|undefined} function that calls the given method 686 * in the given scope with all of its arguments properly attached, or 687 * undefined if there was a problem with the arguments 688 */ 689 ilib.bind = function(scope, method/*, bound arguments*/){ 690 if (!scope || !method) { 691 return undefined; 692 } 693 694 /** @protected 695 * @param {Arguments} inArrayLike 696 * @param {number=} inOffset 697 */ 698 function cloneArray(inArrayLike, inOffset) { 699 var arr = []; 700 for(var i = inOffset || 0, l = inArrayLike.length; i<l; i++){ 701 arr.push(inArrayLike[i]); 702 } 703 return arr; 704 } 705 706 if (typeof(method) === 'function') { 707 var func, args = cloneArray(arguments, 2); 708 if (typeof(method.bind) === 'function') { 709 func = method.bind.apply(method, [scope].concat(args)); 710 } else { 711 func = function() { 712 var nargs = cloneArray(arguments); 713 // invoke with collected args 714 return method.apply(scope, args.concat(nargs)); 715 }; 716 } 717 return func; 718 } 719 return undefined; 720 }; 721 722 /** 723 * @private 724 */ 725 ilib._dyncode = false; 726 727 /** 728 * Return true if this copy of ilib is using dynamically loaded code. It returns 729 * false for pre-assembled code. 730 * 731 * @static 732 * @return {boolean} true if this ilib uses dynamically loaded code, and false otherwise 733 */ 734 ilib.isDynCode = function() { 735 return ilib._dyncode; 736 }; 737 738 /** 739 * @private 740 */ 741 ilib._dyndata = false; 742 743 /** 744 * Return true if this copy of ilib is using dynamically loaded locale data. It returns 745 * false for pre-assembled data. 746 * 747 * @static 748 * @return {boolean} true if this ilib uses dynamically loaded locale data, and false otherwise 749 */ 750 ilib.isDynData = function() { 751 return ilib._dyndata; 752 }; 753 754 ilib._loadtime = new Date().getTime(); 755 756 /*< JSUtils.js */ 757 /* 758 * JSUtils.js - Misc utilities to work around Javascript engine differences 759 * 760 * Copyright © 2013-2015, JEDLSoft 761 * 762 * Licensed under the Apache License, Version 2.0 (the "License"); 763 * you may not use this file except in compliance with the License. 764 * You may obtain a copy of the License at 765 * 766 * http://www.apache.org/licenses/LICENSE-2.0 767 * 768 * Unless required by applicable law or agreed to in writing, software 769 * distributed under the License is distributed on an "AS IS" BASIS, 770 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 771 * 772 * See the License for the specific language governing permissions and 773 * limitations under the License. 774 */ 775 776 // !depends ilib.js 777 778 779 var JSUtils = {}; 780 781 /** 782 * Perform a shallow copy of the source object to the target object. This only 783 * copies the assignments of the source properties to the target properties, 784 * but not recursively from there.<p> 785 * 786 * 787 * @static 788 * @param {Object} source the source object to copy properties from 789 * @param {Object} target the target object to copy properties into 790 */ 791 JSUtils.shallowCopy = function (source, target) { 792 var prop = undefined; 793 if (source && target) { 794 for (prop in source) { 795 if (prop !== undefined && typeof(source[prop]) !== 'undefined') { 796 target[prop] = source[prop]; 797 } 798 } 799 } 800 }; 801 802 /** 803 * Perform a recursive deep copy from the "from" object to the "deep" object. 804 * 805 * @static 806 * @param {Object} from the object to copy from 807 * @param {Object} to the object to copy to 808 * @return {Object} a reference to the the "to" object 809 */ 810 JSUtils.deepCopy = function(from, to) { 811 var prop; 812 813 for (prop in from) { 814 if (prop) { 815 if (typeof(from[prop]) === 'object') { 816 to[prop] = {}; 817 JSUtils.deepCopy(from[prop], to[prop]); 818 } else { 819 to[prop] = from[prop]; 820 } 821 } 822 } 823 return to; 824 }; 825 826 /** 827 * Map a string to the given set of alternate characters. If the target set 828 * does not contain a particular character in the input string, then that 829 * character will be copied to the output unmapped. 830 * 831 * @static 832 * @param {string} str a string to map to an alternate set of characters 833 * @param {Array.<string>|Object} map a mapping to alternate characters 834 * @return {string} the source string where each character is mapped to alternate characters 835 */ 836 JSUtils.mapString = function (str, map) { 837 var mapped = ""; 838 if (map && str) { 839 for (var i = 0; i < str.length; i++) { 840 var c = str.charAt(i); // TODO use a char iterator? 841 mapped += map[c] || c; 842 } 843 } else { 844 mapped = str; 845 } 846 return mapped; 847 }; 848 849 /** 850 * Check if an object is a member of the given array. If this javascript engine 851 * support indexOf, it is used directly. Otherwise, this function implements it 852 * itself. The idea is to make sure that you can use the quick indexOf if it is 853 * available, but use a slower implementation in older engines as well. 854 * 855 * @static 856 * @param {Array.<Object|string|number>} array array to search 857 * @param {Object|string|number} obj object being sought. This should be of the same type as the 858 * members of the array being searched. If not, this function will not return 859 * any results. 860 * @return {number} index of the object in the array, or -1 if it is not in the array. 861 */ 862 JSUtils.indexOf = function(array, obj) { 863 if (!array || !obj) { 864 return -1; 865 } 866 if (typeof(array.indexOf) === 'function') { 867 return array.indexOf(obj); 868 } else { 869 for (var i = 0; i < array.length; i++) { 870 if (array[i] === obj) { 871 return i; 872 } 873 } 874 return -1; 875 } 876 }; 877 878 /** 879 * Pad the str with zeros to the given length of digits. 880 * 881 * @static 882 * @param {string|number} str the string or number to pad 883 * @param {number} length the desired total length of the output string, padded 884 * @param {boolean=} right if true, pad on the right side of the number rather than the left. 885 * Default is false. 886 */ 887 JSUtils.pad = function (str, length, right) { 888 if (typeof(str) !== 'string') { 889 str = "" + str; 890 } 891 var start = 0; 892 // take care of negative numbers 893 if (str.charAt(0) === '-') { 894 start++; 895 } 896 return (str.length >= length+start) ? str : 897 (right ? str + JSUtils.pad.zeros.substring(0,length-str.length+start) : 898 str.substring(0, start) + JSUtils.pad.zeros.substring(0,length-str.length+start) + str.substring(start)); 899 }; 900 901 /** @private */ 902 JSUtils.pad.zeros = "00000000000000000000000000000000"; 903 904 /** 905 * Convert a string into the hexadecimal representation 906 * of the Unicode characters in that string. 907 * 908 * @static 909 * @param {string} string The string to convert 910 * @param {number=} limit the number of digits to use to represent the character (1 to 8) 911 * @return {string} a hexadecimal representation of the 912 * Unicode characters in the input string 913 */ 914 JSUtils.toHexString = function(string, limit) { 915 var i, 916 result = "", 917 lim = (limit && limit < 9) ? limit : 4; 918 919 if (!string) { 920 return ""; 921 } 922 for (i = 0; i < string.length; i++) { 923 var ch = string.charCodeAt(i).toString(16); 924 result += JSUtils.pad(ch, lim); 925 } 926 return result.toUpperCase(); 927 }; 928 929 /** 930 * Test whether an object in a Javascript Date. 931 * 932 * @static 933 * @param {Object|null|undefined} object The object to test 934 * @return {boolean} return true if the object is a Date 935 * and false otherwise 936 */ 937 JSUtils.isDate = function(object) { 938 if (typeof(object) === 'object') { 939 return Object.prototype.toString.call(object) === '[object Date]'; 940 } 941 return false; 942 }; 943 944 /** 945 * Merge the properties of object2 into object1 in a deep manner and return a merged 946 * object. If the property exists in both objects, the value in object2 will overwrite 947 * the value in object1. If a property exists in object1, but not in object2, its value 948 * will not be touched. If a property exists in object2, but not in object1, it will be 949 * added to the merged result.<p> 950 * 951 * Name1 and name2 are for creating debug output only. They are not necessary.<p> 952 * 953 * 954 * @static 955 * @param {*} object1 the object to merge into 956 * @param {*} object2 the object to merge 957 * @param {boolean=} replace if true, replace the array elements in object1 with those in object2. 958 * If false, concatenate array elements in object1 with items in object2. 959 * @param {string=} name1 name of the object being merged into 960 * @param {string=} name2 name of the object being merged in 961 * @return {Object} the merged object 962 */ 963 JSUtils.merge = function (object1, object2, replace, name1, name2) { 964 var prop = undefined, 965 newObj = {}; 966 for (prop in object1) { 967 if (prop && typeof(object1[prop]) !== 'undefined') { 968 newObj[prop] = object1[prop]; 969 } 970 } 971 for (prop in object2) { 972 if (prop && typeof(object2[prop]) !== 'undefined') { 973 if (ilib.isArray(object1[prop]) && ilib.isArray(object2[prop])) { 974 if (typeof(replace) !== 'boolean' || !replace) { 975 newObj[prop] = [].concat(object1[prop]); 976 newObj[prop] = newObj[prop].concat(object2[prop]); 977 } else { 978 newObj[prop] = object2[prop]; 979 } 980 } else if (typeof(object1[prop]) === 'object' && typeof(object2[prop]) === 'object') { 981 newObj[prop] = JSUtils.merge(object1[prop], object2[prop], replace); 982 } else { 983 // for debugging. Used to determine whether or not json files are overriding their parents unnecessarily 984 if (name1 && name2 && newObj[prop] == object2[prop]) { 985 console.log("Property " + prop + " in " + name1 + " is being overridden by the same value in " + name2); 986 } 987 newObj[prop] = object2[prop]; 988 } 989 } 990 } 991 return newObj; 992 }; 993 994 /** 995 * Return true if the given object has no properties.<p> 996 * 997 * 998 * @static 999 * @param {Object} obj the object to check 1000 * @return {boolean} true if the given object has no properties, false otherwise 1001 */ 1002 JSUtils.isEmpty = function (obj) { 1003 var prop = undefined; 1004 1005 if (!obj) { 1006 return true; 1007 } 1008 1009 for (prop in obj) { 1010 if (prop && typeof(obj[prop]) !== 'undefined') { 1011 return false; 1012 } 1013 } 1014 return true; 1015 }; 1016 1017 /** 1018 * @static 1019 */ 1020 JSUtils.hashCode = function(obj) { 1021 var hash = 0; 1022 1023 function addHash(hash, newValue) { 1024 // co-prime numbers creates a nicely distributed hash 1025 hash *= 65543; 1026 hash += newValue; 1027 hash %= 2147483647; 1028 return hash; 1029 } 1030 1031 function stringHash(str) { 1032 var hash = 0; 1033 for (var i = 0; i < str.length; i++) { 1034 hash = addHash(hash, str.charCodeAt(i)); 1035 } 1036 return hash; 1037 } 1038 1039 switch (typeof(obj)) { 1040 case 'undefined': 1041 hash = 0; 1042 break; 1043 case 'string': 1044 hash = stringHash(obj); 1045 break; 1046 case 'function': 1047 case 'number': 1048 case 'xml': 1049 hash = stringHash(String(obj)); 1050 break; 1051 case 'boolean': 1052 hash = obj ? 1 : 0; 1053 break; 1054 case 'object': 1055 var props = []; 1056 for (var p in obj) { 1057 if (obj.hasOwnProperty(p)) { 1058 props.push(p); 1059 } 1060 } 1061 // make sure the order of the properties doesn't matter 1062 props.sort(); 1063 for (var i = 0; i < props.length; i++) { 1064 hash = addHash(hash, stringHash(props[i])); 1065 hash = addHash(hash, JSUtils.hashCode(obj[props[i]])); 1066 } 1067 break; 1068 } 1069 1070 return hash; 1071 }; 1072 1073 1074 1075 1076 /*< Locale.js */ 1077 /* 1078 * Locale.js - Locale specifier definition 1079 * 1080 * Copyright © 2012-2015, JEDLSoft 1081 * 1082 * Licensed under the Apache License, Version 2.0 (the "License"); 1083 * you may not use this file except in compliance with the License. 1084 * You may obtain a copy of the License at 1085 * 1086 * http://www.apache.org/licenses/LICENSE-2.0 1087 * 1088 * Unless required by applicable law or agreed to in writing, software 1089 * distributed under the License is distributed on an "AS IS" BASIS, 1090 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 1091 * 1092 * See the License for the specific language governing permissions and 1093 * limitations under the License. 1094 */ 1095 1096 // !depends ilib.js JSUtils.js 1097 1098 1099 /** 1100 * @class 1101 * Create a new locale instance. Locales are specified either with a specifier string 1102 * that follows the BCP-47 convention (roughly: "language-region-script-variant") or 1103 * with 4 parameters that specify the language, region, variant, and script individually.<p> 1104 * 1105 * The language is given as an ISO 639-1 two-letter, lower-case language code. You 1106 * can find a full list of these codes at 1107 * <a href="http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes">http://en.wikipedia.org/wiki/List_of_ISO_639-1_codes</a><p> 1108 * 1109 * The region is given as an ISO 3166-1 two-letter, upper-case region code. You can 1110 * find a full list of these codes at 1111 * <a href="http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2">http://en.wikipedia.org/wiki/ISO_3166-1_alpha-2</a>.<p> 1112 * 1113 * The variant is any string that does not contain a dash which further differentiates 1114 * locales from each other.<p> 1115 * 1116 * The script is given as the ISO 15924 four-letter script code. In some locales, 1117 * text may be validly written in more than one script. For example, Serbian is often 1118 * written in both Latin and Cyrillic, though not usually mixed together. You can find a 1119 * full list of these codes at 1120 * <a href="http://en.wikipedia.org/wiki/ISO_15924#List_of_codes">http://en.wikipedia.org/wiki/ISO_15924#List_of_codes</a>.<p> 1121 * 1122 * As an example in ilib, the script can be used in the date formatter. Dates formatted 1123 * in Serbian could have day-of-week names or month names written in the Latin 1124 * or Cyrillic script. Often one script is default such that sr-SR-Latn is the same 1125 * as sr-SR so the script code "Latn" can be left off of the locale spec.<p> 1126 * 1127 * Each part is optional, and an empty string in the specifier before or after a 1128 * dash or as a parameter to the constructor denotes an unspecified value. In this 1129 * case, many of the ilib functions will treat the locale as generic. For example 1130 * the locale "en-" is equivalent to "en" and to "en--" and denotes a locale 1131 * of "English" with an unspecified region and variant, which typically matches 1132 * any region or variant.<p> 1133 * 1134 * Without any arguments to the constructor, this function returns the locale of 1135 * the host Javascript engine.<p> 1136 * 1137 * 1138 * @constructor 1139 * @param {?string|Locale=} language the ISO 639 2-letter code for the language, or a full 1140 * locale spec in BCP-47 format, or another Locale instance to copy from 1141 * @param {string=} region the ISO 3166 2-letter code for the region 1142 * @param {string=} variant the name of the variant of this locale, if any 1143 * @param {string=} script the ISO 15924 code of the script for this locale, if any 1144 */ 1145 var Locale = function(language, region, variant, script) { 1146 if (typeof(region) === 'undefined' && typeof(variant) === 'undefined' && typeof(script) === 'undefined') { 1147 var spec = language || ilib.getLocale(); 1148 if (typeof(spec) === 'string') { 1149 var parts = spec.split('-'); 1150 for ( var i = 0; i < parts.length; i++ ) { 1151 if (Locale._isLanguageCode(parts[i])) { 1152 /** 1153 * @private 1154 * @type {string|undefined} 1155 */ 1156 this.language = parts[i]; 1157 } else if (Locale._isRegionCode(parts[i])) { 1158 /** 1159 * @private 1160 * @type {string|undefined} 1161 */ 1162 this.region = parts[i]; 1163 } else if (Locale._isScriptCode(parts[i])) { 1164 /** 1165 * @private 1166 * @type {string|undefined} 1167 */ 1168 this.script = parts[i]; 1169 } else { 1170 /** 1171 * @private 1172 * @type {string|undefined} 1173 */ 1174 this.variant = parts[i]; 1175 } 1176 } 1177 this.language = this.language || undefined; 1178 this.region = this.region || undefined; 1179 this.script = this.script || undefined; 1180 this.variant = this.variant || undefined; 1181 } else if (typeof(spec) === 'object') { 1182 this.language = spec.language || undefined; 1183 this.region = spec.region || undefined; 1184 this.script = spec.script || undefined; 1185 this.variant = spec.variant || undefined; 1186 } 1187 } else { 1188 if (language && typeof(language) === "string") { 1189 language = language.trim(); 1190 this.language = language.length > 0 ? language.toLowerCase() : undefined; 1191 } else { 1192 this.language = undefined; 1193 } 1194 if (region && typeof(region) === "string") { 1195 region = region.trim(); 1196 this.region = region.length > 0 ? region.toUpperCase() : undefined; 1197 } else { 1198 this.region = undefined; 1199 } 1200 if (variant && typeof(variant) === "string") { 1201 variant = variant.trim(); 1202 this.variant = variant.length > 0 ? variant : undefined; 1203 } else { 1204 this.variant = undefined; 1205 } 1206 if (script && typeof(script) === "string") { 1207 script = script.trim(); 1208 this.script = script.length > 0 ? script : undefined; 1209 } else { 1210 this.script = undefined; 1211 } 1212 } 1213 this._genSpec(); 1214 }; 1215 1216 // from http://en.wikipedia.org/wiki/ISO_3166-1 1217 Locale.a2toa3regmap = { 1218 "AF": "AFG", 1219 "AX": "ALA", 1220 "AL": "ALB", 1221 "DZ": "DZA", 1222 "AS": "ASM", 1223 "AD": "AND", 1224 "AO": "AGO", 1225 "AI": "AIA", 1226 "AQ": "ATA", 1227 "AG": "ATG", 1228 "AR": "ARG", 1229 "AM": "ARM", 1230 "AW": "ABW", 1231 "AU": "AUS", 1232 "AT": "AUT", 1233 "AZ": "AZE", 1234 "BS": "BHS", 1235 "BH": "BHR", 1236 "BD": "BGD", 1237 "BB": "BRB", 1238 "BY": "BLR", 1239 "BE": "BEL", 1240 "BZ": "BLZ", 1241 "BJ": "BEN", 1242 "BM": "BMU", 1243 "BT": "BTN", 1244 "BO": "BOL", 1245 "BQ": "BES", 1246 "BA": "BIH", 1247 "BW": "BWA", 1248 "BV": "BVT", 1249 "BR": "BRA", 1250 "IO": "IOT", 1251 "BN": "BRN", 1252 "BG": "BGR", 1253 "BF": "BFA", 1254 "BI": "BDI", 1255 "KH": "KHM", 1256 "CM": "CMR", 1257 "CA": "CAN", 1258 "CV": "CPV", 1259 "KY": "CYM", 1260 "CF": "CAF", 1261 "TD": "TCD", 1262 "CL": "CHL", 1263 "CN": "CHN", 1264 "CX": "CXR", 1265 "CC": "CCK", 1266 "CO": "COL", 1267 "KM": "COM", 1268 "CG": "COG", 1269 "CD": "COD", 1270 "CK": "COK", 1271 "CR": "CRI", 1272 "CI": "CIV", 1273 "HR": "HRV", 1274 "CU": "CUB", 1275 "CW": "CUW", 1276 "CY": "CYP", 1277 "CZ": "CZE", 1278 "DK": "DNK", 1279 "DJ": "DJI", 1280 "DM": "DMA", 1281 "DO": "DOM", 1282 "EC": "ECU", 1283 "EG": "EGY", 1284 "SV": "SLV", 1285 "GQ": "GNQ", 1286 "ER": "ERI", 1287 "EE": "EST", 1288 "ET": "ETH", 1289 "FK": "FLK", 1290 "FO": "FRO", 1291 "FJ": "FJI", 1292 "FI": "FIN", 1293 "FR": "FRA", 1294 "GF": "GUF", 1295 "PF": "PYF", 1296 "TF": "ATF", 1297 "GA": "GAB", 1298 "GM": "GMB", 1299 "GE": "GEO", 1300 "DE": "DEU", 1301 "GH": "GHA", 1302 "GI": "GIB", 1303 "GR": "GRC", 1304 "GL": "GRL", 1305 "GD": "GRD", 1306 "GP": "GLP", 1307 "GU": "GUM", 1308 "GT": "GTM", 1309 "GG": "GGY", 1310 "GN": "GIN", 1311 "GW": "GNB", 1312 "GY": "GUY", 1313 "HT": "HTI", 1314 "HM": "HMD", 1315 "VA": "VAT", 1316 "HN": "HND", 1317 "HK": "HKG", 1318 "HU": "HUN", 1319 "IS": "ISL", 1320 "IN": "IND", 1321 "ID": "IDN", 1322 "IR": "IRN", 1323 "IQ": "IRQ", 1324 "IE": "IRL", 1325 "IM": "IMN", 1326 "IL": "ISR", 1327 "IT": "ITA", 1328 "JM": "JAM", 1329 "JP": "JPN", 1330 "JE": "JEY", 1331 "JO": "JOR", 1332 "KZ": "KAZ", 1333 "KE": "KEN", 1334 "KI": "KIR", 1335 "KP": "PRK", 1336 "KR": "KOR", 1337 "KW": "KWT", 1338 "KG": "KGZ", 1339 "LA": "LAO", 1340 "LV": "LVA", 1341 "LB": "LBN", 1342 "LS": "LSO", 1343 "LR": "LBR", 1344 "LY": "LBY", 1345 "LI": "LIE", 1346 "LT": "LTU", 1347 "LU": "LUX", 1348 "MO": "MAC", 1349 "MK": "MKD", 1350 "MG": "MDG", 1351 "MW": "MWI", 1352 "MY": "MYS", 1353 "MV": "MDV", 1354 "ML": "MLI", 1355 "MT": "MLT", 1356 "MH": "MHL", 1357 "MQ": "MTQ", 1358 "MR": "MRT", 1359 "MU": "MUS", 1360 "YT": "MYT", 1361 "MX": "MEX", 1362 "FM": "FSM", 1363 "MD": "MDA", 1364 "MC": "MCO", 1365 "MN": "MNG", 1366 "ME": "MNE", 1367 "MS": "MSR", 1368 "MA": "MAR", 1369 "MZ": "MOZ", 1370 "MM": "MMR", 1371 "NA": "NAM", 1372 "NR": "NRU", 1373 "NP": "NPL", 1374 "NL": "NLD", 1375 "NC": "NCL", 1376 "NZ": "NZL", 1377 "NI": "NIC", 1378 "NE": "NER", 1379 "NG": "NGA", 1380 "NU": "NIU", 1381 "NF": "NFK", 1382 "MP": "MNP", 1383 "NO": "NOR", 1384 "OM": "OMN", 1385 "PK": "PAK", 1386 "PW": "PLW", 1387 "PS": "PSE", 1388 "PA": "PAN", 1389 "PG": "PNG", 1390 "PY": "PRY", 1391 "PE": "PER", 1392 "PH": "PHL", 1393 "PN": "PCN", 1394 "PL": "POL", 1395 "PT": "PRT", 1396 "PR": "PRI", 1397 "QA": "QAT", 1398 "RE": "REU", 1399 "RO": "ROU", 1400 "RU": "RUS", 1401 "RW": "RWA", 1402 "BL": "BLM", 1403 "SH": "SHN", 1404 "KN": "KNA", 1405 "LC": "LCA", 1406 "MF": "MAF", 1407 "PM": "SPM", 1408 "VC": "VCT", 1409 "WS": "WSM", 1410 "SM": "SMR", 1411 "ST": "STP", 1412 "SA": "SAU", 1413 "SN": "SEN", 1414 "RS": "SRB", 1415 "SC": "SYC", 1416 "SL": "SLE", 1417 "SG": "SGP", 1418 "SX": "SXM", 1419 "SK": "SVK", 1420 "SI": "SVN", 1421 "SB": "SLB", 1422 "SO": "SOM", 1423 "ZA": "ZAF", 1424 "GS": "SGS", 1425 "SS": "SSD", 1426 "ES": "ESP", 1427 "LK": "LKA", 1428 "SD": "SDN", 1429 "SR": "SUR", 1430 "SJ": "SJM", 1431 "SZ": "SWZ", 1432 "SE": "SWE", 1433 "CH": "CHE", 1434 "SY": "SYR", 1435 "TW": "TWN", 1436 "TJ": "TJK", 1437 "TZ": "TZA", 1438 "TH": "THA", 1439 "TL": "TLS", 1440 "TG": "TGO", 1441 "TK": "TKL", 1442 "TO": "TON", 1443 "TT": "TTO", 1444 "TN": "TUN", 1445 "TR": "TUR", 1446 "TM": "TKM", 1447 "TC": "TCA", 1448 "TV": "TUV", 1449 "UG": "UGA", 1450 "UA": "UKR", 1451 "AE": "ARE", 1452 "GB": "GBR", 1453 "US": "USA", 1454 "UM": "UMI", 1455 "UY": "URY", 1456 "UZ": "UZB", 1457 "VU": "VUT", 1458 "VE": "VEN", 1459 "VN": "VNM", 1460 "VG": "VGB", 1461 "VI": "VIR", 1462 "WF": "WLF", 1463 "EH": "ESH", 1464 "YE": "YEM", 1465 "ZM": "ZMB", 1466 "ZW": "ZWE" 1467 }; 1468 1469 1470 Locale.a1toa3langmap = { 1471 "ab": "abk", 1472 "aa": "aar", 1473 "af": "afr", 1474 "ak": "aka", 1475 "sq": "sqi", 1476 "am": "amh", 1477 "ar": "ara", 1478 "an": "arg", 1479 "hy": "hye", 1480 "as": "asm", 1481 "av": "ava", 1482 "ae": "ave", 1483 "ay": "aym", 1484 "az": "aze", 1485 "bm": "bam", 1486 "ba": "bak", 1487 "eu": "eus", 1488 "be": "bel", 1489 "bn": "ben", 1490 "bh": "bih", 1491 "bi": "bis", 1492 "bs": "bos", 1493 "br": "bre", 1494 "bg": "bul", 1495 "my": "mya", 1496 "ca": "cat", 1497 "ch": "cha", 1498 "ce": "che", 1499 "ny": "nya", 1500 "zh": "zho", 1501 "cv": "chv", 1502 "kw": "cor", 1503 "co": "cos", 1504 "cr": "cre", 1505 "hr": "hrv", 1506 "cs": "ces", 1507 "da": "dan", 1508 "dv": "div", 1509 "nl": "nld", 1510 "dz": "dzo", 1511 "en": "eng", 1512 "eo": "epo", 1513 "et": "est", 1514 "ee": "ewe", 1515 "fo": "fao", 1516 "fj": "fij", 1517 "fi": "fin", 1518 "fr": "fra", 1519 "ff": "ful", 1520 "gl": "glg", 1521 "ka": "kat", 1522 "de": "deu", 1523 "el": "ell", 1524 "gn": "grn", 1525 "gu": "guj", 1526 "ht": "hat", 1527 "ha": "hau", 1528 "he": "heb", 1529 "hz": "her", 1530 "hi": "hin", 1531 "ho": "hmo", 1532 "hu": "hun", 1533 "ia": "ina", 1534 "id": "ind", 1535 "ie": "ile", 1536 "ga": "gle", 1537 "ig": "ibo", 1538 "ik": "ipk", 1539 "io": "ido", 1540 "is": "isl", 1541 "it": "ita", 1542 "iu": "iku", 1543 "ja": "jpn", 1544 "jv": "jav", 1545 "kl": "kal", 1546 "kn": "kan", 1547 "kr": "kau", 1548 "ks": "kas", 1549 "kk": "kaz", 1550 "km": "khm", 1551 "ki": "kik", 1552 "rw": "kin", 1553 "ky": "kir", 1554 "kv": "kom", 1555 "kg": "kon", 1556 "ko": "kor", 1557 "ku": "kur", 1558 "kj": "kua", 1559 "la": "lat", 1560 "lb": "ltz", 1561 "lg": "lug", 1562 "li": "lim", 1563 "ln": "lin", 1564 "lo": "lao", 1565 "lt": "lit", 1566 "lu": "lub", 1567 "lv": "lav", 1568 "gv": "glv", 1569 "mk": "mkd", 1570 "mg": "mlg", 1571 "ms": "msa", 1572 "ml": "mal", 1573 "mt": "mlt", 1574 "mi": "mri", 1575 "mr": "mar", 1576 "mh": "mah", 1577 "mn": "mon", 1578 "na": "nau", 1579 "nv": "nav", 1580 "nb": "nob", 1581 "nd": "nde", 1582 "ne": "nep", 1583 "ng": "ndo", 1584 "nn": "nno", 1585 "no": "nor", 1586 "ii": "iii", 1587 "nr": "nbl", 1588 "oc": "oci", 1589 "oj": "oji", 1590 "cu": "chu", 1591 "om": "orm", 1592 "or": "ori", 1593 "os": "oss", 1594 "pa": "pan", 1595 "pi": "pli", 1596 "fa": "fas", 1597 "pl": "pol", 1598 "ps": "pus", 1599 "pt": "por", 1600 "qu": "que", 1601 "rm": "roh", 1602 "rn": "run", 1603 "ro": "ron", 1604 "ru": "rus", 1605 "sa": "san", 1606 "sc": "srd", 1607 "sd": "snd", 1608 "se": "sme", 1609 "sm": "smo", 1610 "sg": "sag", 1611 "sr": "srp", 1612 "gd": "gla", 1613 "sn": "sna", 1614 "si": "sin", 1615 "sk": "slk", 1616 "sl": "slv", 1617 "so": "som", 1618 "st": "sot", 1619 "es": "spa", 1620 "su": "sun", 1621 "sw": "swa", 1622 "ss": "ssw", 1623 "sv": "swe", 1624 "ta": "tam", 1625 "te": "tel", 1626 "tg": "tgk", 1627 "th": "tha", 1628 "ti": "tir", 1629 "bo": "bod", 1630 "tk": "tuk", 1631 "tl": "tgl", 1632 "tn": "tsn", 1633 "to": "ton", 1634 "tr": "tur", 1635 "ts": "tso", 1636 "tt": "tat", 1637 "tw": "twi", 1638 "ty": "tah", 1639 "ug": "uig", 1640 "uk": "ukr", 1641 "ur": "urd", 1642 "uz": "uzb", 1643 "ve": "ven", 1644 "vi": "vie", 1645 "vo": "vol", 1646 "wa": "wln", 1647 "cy": "cym", 1648 "wo": "wol", 1649 "fy": "fry", 1650 "xh": "xho", 1651 "yi": "yid", 1652 "yo": "yor", 1653 "za": "zha", 1654 "zu": "zul" 1655 }; 1656 1657 /** 1658 * Tell whether or not the str does not start with a lower case ASCII char. 1659 * @private 1660 * @param {string} str the char to check 1661 * @return {boolean} true if the char is not a lower case ASCII char 1662 */ 1663 Locale._notLower = function(str) { 1664 // do this with ASCII only so we don't have to depend on the CType functions 1665 var ch = str.charCodeAt(0); 1666 return ch < 97 || ch > 122; 1667 }; 1668 1669 /** 1670 * Tell whether or not the str does not start with an upper case ASCII char. 1671 * @private 1672 * @param {string} str the char to check 1673 * @return {boolean} true if the char is a not an upper case ASCII char 1674 */ 1675 Locale._notUpper = function(str) { 1676 // do this with ASCII only so we don't have to depend on the CType functions 1677 var ch = str.charCodeAt(0); 1678 return ch < 65 || ch > 90; 1679 }; 1680 1681 /** 1682 * Tell whether or not the str does not start with a digit char. 1683 * @private 1684 * @param {string} str the char to check 1685 * @return {boolean} true if the char is a not an upper case ASCII char 1686 */ 1687 Locale._notDigit = function(str) { 1688 // do this with ASCII only so we don't have to depend on the CType functions 1689 var ch = str.charCodeAt(0); 1690 return ch < 48 || ch > 57; 1691 }; 1692 1693 /** 1694 * Tell whether or not the given string has the correct syntax to be 1695 * an ISO 639 language code. 1696 * 1697 * @private 1698 * @param {string} str the string to parse 1699 * @return {boolean} true if the string could syntactically be a language code. 1700 */ 1701 Locale._isLanguageCode = function(str) { 1702 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1703 return false; 1704 } 1705 1706 for (var i = 0; i < str.length; i++) { 1707 if (Locale._notLower(str.charAt(i))) { 1708 return false; 1709 } 1710 } 1711 1712 return true; 1713 }; 1714 1715 /** 1716 * Tell whether or not the given string has the correct syntax to be 1717 * an ISO 3166 2-letter region code or M.49 3-digit region code. 1718 * 1719 * @private 1720 * @param {string} str the string to parse 1721 * @return {boolean} true if the string could syntactically be a language code. 1722 */ 1723 Locale._isRegionCode = function (str) { 1724 var i; 1725 1726 if (typeof(str) === 'undefined' || str.length < 2 || str.length > 3) { 1727 return false; 1728 } 1729 1730 if (str.length === 2) { 1731 for (i = 0; i < str.length; i++) { 1732 if (Locale._notUpper(str.charAt(i))) { 1733 return false; 1734 } 1735 } 1736 } else { 1737 for (i = 0; i < str.length; i++) { 1738 if (Locale._notDigit(str.charAt(i))) { 1739 return false; 1740 } 1741 } 1742 } 1743 1744 return true; 1745 }; 1746 1747 /** 1748 * Tell whether or not the given string has the correct syntax to be 1749 * an ISO 639 language code. 1750 * 1751 * @private 1752 * @param {string} str the string to parse 1753 * @return {boolean} true if the string could syntactically be a language code. 1754 */ 1755 Locale._isScriptCode = function(str) { 1756 if (typeof(str) === 'undefined' || str.length !== 4 || Locale._notUpper(str.charAt(0))) { 1757 return false; 1758 } 1759 1760 for (var i = 1; i < 4; i++) { 1761 if (Locale._notLower(str.charAt(i))) { 1762 return false; 1763 } 1764 } 1765 1766 return true; 1767 }; 1768 1769 /** 1770 * Return the ISO-3166 alpha3 equivalent region code for the given ISO 3166 alpha2 1771 * region code. If the given alpha2 code is not found, this function returns its 1772 * argument unchanged. 1773 * @static 1774 * @param {string|undefined} alpha2 the alpha2 code to map 1775 * @return {string|undefined} the alpha3 equivalent of the given alpha2 code, or the alpha2 1776 * parameter if the alpha2 value is not found 1777 */ 1778 Locale.regionAlpha2ToAlpha3 = function(alpha2) { 1779 return Locale.a2toa3regmap[alpha2] || alpha2; 1780 }; 1781 1782 /** 1783 * Return the ISO-639 alpha3 equivalent language code for the given ISO 639 alpha1 1784 * language code. If the given alpha1 code is not found, this function returns its 1785 * argument unchanged. 1786 * @static 1787 * @param {string|undefined} alpha1 the alpha1 code to map 1788 * @return {string|undefined} the alpha3 equivalent of the given alpha1 code, or the alpha1 1789 * parameter if the alpha1 value is not found 1790 */ 1791 Locale.languageAlpha1ToAlpha3 = function(alpha1) { 1792 return Locale.a1toa3langmap[alpha1] || alpha1; 1793 }; 1794 1795 Locale.prototype = { 1796 /** 1797 * @private 1798 */ 1799 _genSpec: function () { 1800 this.spec = this.language || ""; 1801 1802 if (this.script) { 1803 if (this.spec.length > 0) { 1804 this.spec += "-"; 1805 } 1806 this.spec += this.script; 1807 } 1808 1809 if (this.region) { 1810 if (this.spec.length > 0) { 1811 this.spec += "-"; 1812 } 1813 this.spec += this.region; 1814 } 1815 1816 if (this.variant) { 1817 if (this.spec.length > 0) { 1818 this.spec += "-"; 1819 } 1820 this.spec += this.variant; 1821 } 1822 }, 1823 1824 /** 1825 * Return the ISO 639 language code for this locale. 1826 * @return {string|undefined} the language code for this locale 1827 */ 1828 getLanguage: function() { 1829 return this.language; 1830 }, 1831 1832 /** 1833 * Return the language of this locale as an ISO-639-alpha3 language code 1834 * @return {string|undefined} the alpha3 language code of this locale 1835 */ 1836 getLanguageAlpha3: function() { 1837 return Locale.languageAlpha1ToAlpha3(this.language); 1838 }, 1839 1840 /** 1841 * Return the ISO 3166 region code for this locale. 1842 * @return {string|undefined} the region code of this locale 1843 */ 1844 getRegion: function() { 1845 return this.region; 1846 }, 1847 1848 /** 1849 * Return the region of this locale as an ISO-3166-alpha3 region code 1850 * @return {string|undefined} the alpha3 region code of this locale 1851 */ 1852 getRegionAlpha3: function() { 1853 return Locale.regionAlpha2ToAlpha3(this.region); 1854 }, 1855 1856 /** 1857 * Return the ISO 15924 script code for this locale 1858 * @return {string|undefined} the script code of this locale 1859 */ 1860 getScript: function () { 1861 return this.script; 1862 }, 1863 1864 /** 1865 * Return the variant code for this locale 1866 * @return {string|undefined} the variant code of this locale, if any 1867 */ 1868 getVariant: function() { 1869 return this.variant; 1870 }, 1871 1872 /** 1873 * Return the whole locale specifier as a string. 1874 * @return {string} the locale specifier 1875 */ 1876 getSpec: function() { 1877 if (!this.spec) this._genSpec(); 1878 return this.spec; 1879 }, 1880 1881 /** 1882 * Return the language locale specifier. This includes the 1883 * language and the script if it is available. This can be 1884 * used to see whether the written language of two locales 1885 * match each other regardless of the region or variant. 1886 * 1887 * @return {string} the language locale specifier 1888 */ 1889 getLangSpec: function() { 1890 var spec = this.language; 1891 if (spec && this.script) { 1892 spec += "-" + this.script; 1893 } 1894 return spec || ""; 1895 }, 1896 1897 /** 1898 * Express this locale object as a string. Currently, this simply calls the getSpec 1899 * function to represent the locale as its specifier. 1900 * 1901 * @return {string} the locale specifier 1902 */ 1903 toString: function() { 1904 return this.getSpec(); 1905 }, 1906 1907 /** 1908 * Return true if the the other locale is exactly equal to the current one. 1909 * @return {boolean} whether or not the other locale is equal to the current one 1910 */ 1911 equals: function(other) { 1912 return this.language === other.language && 1913 this.region === other.region && 1914 this.script === other.script && 1915 this.variant === other.variant; 1916 }, 1917 1918 /** 1919 * Return true if the current locale is the special pseudo locale. 1920 * @return {boolean} true if the current locale is the special pseudo locale 1921 */ 1922 isPseudo: function () { 1923 return JSUtils.indexOf(ilib.pseudoLocales, this.spec) > -1; 1924 } 1925 }; 1926 1927 // static functions 1928 /** 1929 * @private 1930 */ 1931 Locale.locales = [ 1932 1933 ]; 1934 1935 /** 1936 * Return the list of available locales that this iLib file supports. 1937 * If this copy of ilib is pre-assembled with locale data, then the 1938 * list locales may be much smaller 1939 * than the list of all available locales in the iLib repository. The 1940 * assembly tool will automatically fill in the list for an assembled 1941 * copy of iLib. If this copy is being used with dynamically loaded 1942 * data, then you 1943 * can load any locale that iLib supports. You can form a locale with any 1944 * combination of a language and region tags that exist in the locale 1945 * data directory. Language tags are in the root of the locale data dir, 1946 * and region tags can be found underneath the "und" directory. (The 1947 * region tags are separated into a different dir because the region names 1948 * conflict with language names on file systems that are case-insensitive.) 1949 * If you have culled the locale data directory to limit the size of 1950 * your app, then this function should return only those files that actually exist 1951 * according to the ilibmanifest.json file in the root of that locale 1952 * data dir. Make sure your ilibmanifest.json file is up-to-date with 1953 * respect to the list of files that exist in the locale data dir. 1954 * 1955 * @param {boolean} sync if false, load the list of available files from disk 1956 * asynchronously, otherwise load them synchronously. (Default: true/synchronously) 1957 * @param {Function} onLoad a callback function to call if asynchronous 1958 * load was requested and the list of files have been loaded. 1959 * @return {Array.<string>} this is an array of locale specs for which 1960 * this iLib file has locale data for 1961 */ 1962 Locale.getAvailableLocales = function (sync, onLoad) { 1963 var locales = []; 1964 if (Locale.locales.length || typeof(ilib._load.listAvailableFiles) !== 'function') { 1965 locales = Locale.locales; 1966 if (onLoad && typeof(onLoad) === 'function') { 1967 onLoad(locales); 1968 } 1969 } else { 1970 if (typeof(sync) === 'undefined') { 1971 sync = true; 1972 } 1973 ilib._load.listAvailableFiles(sync, function(manifest) { 1974 if (manifest) { 1975 for (var dir in manifest) { 1976 var filelist = manifest[dir]; 1977 for (var i = 0; i < filelist.length; i++) { 1978 if (filelist[i].length > 15 && filelist[i].substr(-15) === "localeinfo.json") { 1979 locales.push(filelist[i].substring(0,filelist[i].length-16).replace(/\//g, "-")); 1980 } 1981 } 1982 } 1983 } 1984 if (onLoad && typeof(onLoad) === 'function') { 1985 onLoad(locales); 1986 } 1987 }); 1988 } 1989 return locales; 1990 }; 1991 1992 1993 1994 /*< Utils.js */ 1995 /* 1996 * Utils.js - Core utility routines 1997 * 1998 * Copyright © 2012-2015, JEDLSoft 1999 * 2000 * Licensed under the Apache License, Version 2.0 (the "License"); 2001 * you may not use this file except in compliance with the License. 2002 * You may obtain a copy of the License at 2003 * 2004 * http://www.apache.org/licenses/LICENSE-2.0 2005 * 2006 * Unless required by applicable law or agreed to in writing, software 2007 * distributed under the License is distributed on an "AS IS" BASIS, 2008 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2009 * 2010 * See the License for the specific language governing permissions and 2011 * limitations under the License. 2012 */ 2013 2014 // !depends ilib.js Locale.js JSUtils.js 2015 2016 2017 var Utils = {}; 2018 2019 /** 2020 * Find and merge all the locale data for a particular prefix in the given locale 2021 * and return it as a single javascript object. This merges the data in the 2022 * correct order: 2023 * 2024 * <ol> 2025 * <li>shared data (usually English) 2026 * <li>data for language 2027 * <li>data for language + region 2028 * <li>data for language + region + script 2029 * <li>data for language + region + script + variant 2030 * </ol> 2031 * 2032 * It is okay for any of the above to be missing. This function will just skip the 2033 * missing data. However, if everything except the shared data is missing, this 2034 * function returns undefined, allowing the caller to go and dynamically load the 2035 * data instead. 2036 * 2037 * @static 2038 * @param {string} prefix prefix under ilib.data of the data to merge 2039 * @param {Locale} locale locale of the data being sought 2040 * @param {boolean=} replaceArrays if true, replace the array elements in object1 with those in object2. 2041 * If false, concatenate array elements in object1 with items in object2. 2042 * @param {boolean=} returnOne if true, only return the most locale-specific data. If false, 2043 * merge all the relevant locale data together. 2044 * @return {Object?} the merged locale data 2045 */ 2046 Utils.mergeLocData = function (prefix, locale, replaceArrays, returnOne) { 2047 var data = undefined; 2048 var loc = locale || new Locale(); 2049 var foundLocaleData = false; 2050 var property = prefix; 2051 var mostSpecific; 2052 2053 data = ilib.data[prefix] || {}; 2054 2055 mostSpecific = data; 2056 2057 if (loc.getLanguage()) { 2058 property = prefix + '_' + loc.getLanguage(); 2059 if (ilib.data[property]) { 2060 foundLocaleData = true; 2061 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2062 mostSpecific = ilib.data[property]; 2063 } 2064 } 2065 2066 if (loc.getRegion()) { 2067 property = prefix + '_' + loc.getRegion(); 2068 if (ilib.data[property]) { 2069 foundLocaleData = true; 2070 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2071 mostSpecific = ilib.data[property]; 2072 } 2073 } 2074 2075 if (loc.getLanguage()) { 2076 property = prefix + '_' + loc.getLanguage(); 2077 2078 if (loc.getScript()) { 2079 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript(); 2080 if (ilib.data[property]) { 2081 foundLocaleData = true; 2082 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2083 mostSpecific = ilib.data[property]; 2084 } 2085 } 2086 2087 if (loc.getRegion()) { 2088 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion(); 2089 if (ilib.data[property]) { 2090 foundLocaleData = true; 2091 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2092 mostSpecific = ilib.data[property]; 2093 } 2094 } 2095 } 2096 2097 if (loc.getRegion() && loc.getVariant()) { 2098 property = prefix + '_' + loc.getLanguage() + '_' + loc.getVariant(); 2099 if (ilib.data[property]) { 2100 foundLocaleData = true; 2101 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2102 mostSpecific = ilib.data[property]; 2103 } 2104 } 2105 2106 if (loc.getLanguage() && loc.getScript() && loc.getRegion()) { 2107 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion(); 2108 if (ilib.data[property]) { 2109 foundLocaleData = true; 2110 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2111 mostSpecific = ilib.data[property]; 2112 } 2113 } 2114 2115 if (loc.getLanguage() && loc.getRegion() && loc.getVariant()) { 2116 property = prefix + '_' + loc.getLanguage() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2117 if (ilib.data[property]) { 2118 foundLocaleData = true; 2119 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2120 mostSpecific = ilib.data[property]; 2121 } 2122 } 2123 2124 if (loc.getLanguage() && loc.getScript() && loc.getRegion() && loc.getVariant()) { 2125 property = prefix + '_' + loc.getLanguage() + '_' + loc.getScript() + '_' + loc.getRegion() + '_' + loc.getVariant(); 2126 if (ilib.data[property]) { 2127 foundLocaleData = true; 2128 data = JSUtils.merge(data, ilib.data[property], replaceArrays); 2129 mostSpecific = ilib.data[property]; 2130 } 2131 } 2132 2133 return foundLocaleData ? (returnOne ? mostSpecific : data) : undefined; 2134 }; 2135 2136 /** 2137 * Return an array of relative path names for the 2138 * files that represent the data for the given locale.<p> 2139 * 2140 * Note that to prevent the situation where a directory for 2141 * a language exists next to the directory for a region where 2142 * the language code and region code differ only by case, the 2143 * plain region directories are located under the special 2144 * "undefined" language directory which has the ISO code "und". 2145 * The reason is that some platforms have case-insensitive 2146 * file systems, and you cannot have 2 directories with the 2147 * same name which only differ by case. For example, "es" is 2148 * the ISO 639 code for the language "Spanish" and "ES" is 2149 * the ISO 3166 code for the region "Spain", so both the 2150 * directories cannot exist underneath "locale". The region 2151 * therefore will be loaded from "und/ES" instead.<p> 2152 * 2153 * <h4>Variations</h4> 2154 * 2155 * With only language and region specified, the following 2156 * sequence of paths will be generated:<p> 2157 * 2158 * <pre> 2159 * language 2160 * und/region 2161 * language/region 2162 * </pre> 2163 * 2164 * With only language and script specified:<p> 2165 * 2166 * <pre> 2167 * language 2168 * language/script 2169 * </pre> 2170 * 2171 * With only script and region specified:<p> 2172 * 2173 * <pre> 2174 * und/region 2175 * </pre> 2176 * 2177 * With only region and variant specified:<p> 2178 * 2179 * <pre> 2180 * und/region 2181 * region/variant 2182 * </pre> 2183 * 2184 * With only language, script, and region specified:<p> 2185 * 2186 * <pre> 2187 * language 2188 * und/region 2189 * language/script 2190 * language/region 2191 * language/script/region 2192 * </pre> 2193 * 2194 * With only language, region, and variant specified:<p> 2195 * 2196 * <pre> 2197 * language 2198 * und/region 2199 * language/region 2200 * region/variant 2201 * language/region/variant 2202 * </pre> 2203 * 2204 * With all parts specified:<p> 2205 * 2206 * <pre> 2207 * language 2208 * und/region 2209 * language/script 2210 * language/region 2211 * region/variant 2212 * language/script/region 2213 * language/region/variant 2214 * language/script/region/variant 2215 * </pre> 2216 * 2217 * @static 2218 * @param {Locale} locale load the files for this locale 2219 * @param {string?} name the file name of each file to load without 2220 * any path 2221 * @return {Array.<string>} An array of relative path names 2222 * for the files that contain the locale data 2223 */ 2224 Utils.getLocFiles = function(locale, name) { 2225 var dir = ""; 2226 var files = []; 2227 var filename = name || "resources.json"; 2228 var loc = locale || new Locale(); 2229 2230 var language = loc.getLanguage(); 2231 var region = loc.getRegion(); 2232 var script = loc.getScript(); 2233 var variant = loc.getVariant(); 2234 2235 files.push(filename); // generic shared file 2236 2237 if (language) { 2238 dir = language + "/"; 2239 files.push(dir + filename); 2240 } 2241 2242 if (region) { 2243 dir = "und/" + region + "/"; 2244 files.push(dir + filename); 2245 } 2246 2247 if (language) { 2248 if (script) { 2249 dir = language + "/" + script + "/"; 2250 files.push(dir + filename); 2251 } 2252 if (region) { 2253 dir = language + "/" + region + "/"; 2254 files.push(dir + filename); 2255 } 2256 } 2257 2258 if (region && variant) { 2259 dir = "und/" + region + "/" + variant + "/"; 2260 files.push(dir + filename); 2261 } 2262 2263 if (language && script && region) { 2264 dir = language + "/" + script + "/" + region + "/"; 2265 files.push(dir + filename); 2266 } 2267 2268 if (language && region && variant) { 2269 dir = language + "/" + region + "/" + variant + "/"; 2270 files.push(dir + filename); 2271 } 2272 2273 if (language && script && region && variant) { 2274 dir = language + "/" + script + "/" + region + "/" + variant + "/"; 2275 files.push(dir + filename); 2276 } 2277 2278 return files; 2279 }; 2280 2281 /** 2282 * Load data using the new loader object or via the old function callback. 2283 * @static 2284 * @private 2285 */ 2286 Utils._callLoadData = function (files, sync, params, callback) { 2287 // console.log("Utils._callLoadData called"); 2288 if (typeof(ilib._load) === 'function') { 2289 // console.log("Utils._callLoadData: calling as a regular function"); 2290 return ilib._load(files, sync, params, callback); 2291 } else if (typeof(ilib._load) === 'object' && typeof(ilib._load.loadFiles) === 'function') { 2292 // console.log("Utils._callLoadData: calling as an object"); 2293 return ilib._load.loadFiles(files, sync, params, callback); 2294 } 2295 2296 // console.log("Utils._callLoadData: not calling. Type is " + typeof(ilib._load) + " and instanceof says " + (ilib._load instanceof Loader)); 2297 return undefined; 2298 }; 2299 2300 /** 2301 * Find locale data or load it in. If the data with the given name is preassembled, it will 2302 * find the data in ilib.data. If the data is not preassembled but there is a loader function, 2303 * this function will call it to load the data. Otherwise, the callback will be called with 2304 * undefined as the data. This function will create a cache under the given class object. 2305 * If data was successfully loaded, it will be set into the cache so that future access to 2306 * the same data for the same locale is much quicker.<p> 2307 * 2308 * The parameters can specify any of the following properties:<p> 2309 * 2310 * <ul> 2311 * <li><i>name</i> - String. The name of the file being loaded. Default: ResBundle.json 2312 * <li><i>object</i> - String. The name of the class attempting to load data. This is used to differentiate parts of the cache. 2313 * <li><i>locale</i> - Locale. The locale for which data is loaded. Default is the current locale. 2314 * <li><i>nonlocale</i> - boolean. If true, the data being loaded is not locale-specific. 2315 * <li><i>type</i> - String. Type of file to load. This can be "json" or "other" type. Default: "json" 2316 * <li><i>replace</i> - boolean. When merging json objects, this parameter controls whether to merge arrays 2317 * or have arrays replace each other. If true, arrays in child objects replace the arrays in parent 2318 * objects. When false, the arrays in child objects are concatenated with the arrays in parent objects. 2319 * <li><i>loadParams</i> - Object. An object with parameters to pass to the loader function 2320 * <li><i>sync</i> - boolean. Whether or not to load the data synchronously 2321 * <li><i>callback</i> - function(?)=. callback Call back function to call when the data is available. 2322 * Data is not returned from this method, so a callback function is mandatory. 2323 * </ul> 2324 * 2325 * @static 2326 * @param {Object} params Parameters configuring how to load the files (see above) 2327 */ 2328 Utils.loadData = function(params) { 2329 var name = "resources.json", 2330 object = "generic", 2331 locale = new Locale(ilib.getLocale()), 2332 sync = false, 2333 type = undefined, 2334 loadParams = {}, 2335 callback = undefined, 2336 nonlocale = false, 2337 replace = false, 2338 basename; 2339 2340 if (!params || typeof(params.callback) !== 'function') { 2341 return; 2342 } 2343 2344 if (params.name) { 2345 name = params.name; 2346 } 2347 if (params.object) { 2348 object = params.object; 2349 } 2350 if (params.locale) { 2351 locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 2352 } 2353 if (params.type) { 2354 type = params.type; 2355 } 2356 if (params.loadParams) { 2357 loadParams = params.loadParams; 2358 } 2359 if (params.sync) { 2360 sync = params.sync; 2361 } 2362 if (params.nonlocale) { 2363 nonlocale = !!params.nonlocale; 2364 } 2365 if (typeof(params.replace) === 'boolean') { 2366 replace = params.replace; 2367 } 2368 2369 callback = params.callback; 2370 2371 if (object && !ilib.data.cache[object]) { 2372 ilib.data.cache[object] = {}; 2373 } 2374 2375 if (!type) { 2376 var dot = name.lastIndexOf("."); 2377 type = (dot !== -1) ? name.substring(dot+1) : "text"; 2378 } 2379 2380 var spec = ((!nonlocale && locale.getSpec().replace(/-/g, '_')) || "root") + "," + name + "," + String(JSUtils.hashCode(loadParams)); 2381 if (!object || !ilib.data.cache[object] || typeof(ilib.data.cache[object][spec]) === 'undefined') { 2382 var data, returnOne = (loadParams && loadParams.returnOne); 2383 2384 if (type === "json") { 2385 // console.log("type is json"); 2386 basename = name.substring(0, name.lastIndexOf(".")); 2387 if (nonlocale) { 2388 basename = basename.replace(/[\.:\(\)\/\\\+\-]/g, "_"); 2389 data = ilib.data[basename]; 2390 } else { 2391 data = Utils.mergeLocData(basename, locale, replace, returnOne); 2392 } 2393 if (data) { 2394 // console.log("found assembled data"); 2395 if (object) { 2396 ilib.data.cache[object][spec] = data; 2397 } 2398 callback(data); 2399 return; 2400 } 2401 } 2402 2403 // console.log("ilib._load is " + typeof(ilib._load)); 2404 if (typeof(ilib._load) !== 'undefined') { 2405 // the data is not preassembled, so attempt to load it dynamically 2406 var files = nonlocale ? [ name || "resources.json" ] : Utils.getLocFiles(locale, name); 2407 if (type !== "json") { 2408 loadParams.returnOne = true; 2409 } 2410 2411 Utils._callLoadData(files, sync, loadParams, ilib.bind(this, function(arr) { 2412 if (type === "json") { 2413 data = ilib.data[basename] || {}; 2414 for (var i = 0; i < arr.length; i++) { 2415 if (typeof(arr[i]) !== 'undefined') { 2416 if (loadParams.returnOne) { 2417 data = arr[i]; 2418 break; 2419 } 2420 data = JSUtils.merge(data, arr[i], replace); 2421 } 2422 } 2423 2424 if (object) { 2425 ilib.data.cache[object][spec] = data; 2426 } 2427 callback(data); 2428 } else { 2429 var i = arr.length-1; 2430 while (i > -1 && !arr[i]) { 2431 i--; 2432 } 2433 if (i > -1) { 2434 if (object) { 2435 ilib.data.cache[object][spec] = arr[i]; 2436 } 2437 callback(arr[i]); 2438 } else { 2439 callback(undefined); 2440 } 2441 } 2442 })); 2443 } else { 2444 // no data other than the generic shared data 2445 if (type === "json") { 2446 data = ilib.data[basename]; 2447 } 2448 if (object && data) { 2449 ilib.data.cache[object][spec] = data; 2450 } 2451 callback(data); 2452 } 2453 } else { 2454 callback(ilib.data.cache && ilib.data.cache[object] && ilib.data.cache[object][spec]); 2455 } 2456 }; 2457 2458 2459 /*< MathUtils.js */ 2460 /* 2461 * MathUtils.js - Misc math utility routines 2462 * 2463 * Copyright © 2013-2015, 2018 JEDLSoft 2464 * 2465 * Licensed under the Apache License, Version 2.0 (the "License"); 2466 * you may not use this file except in compliance with the License. 2467 * You may obtain a copy of the License at 2468 * 2469 * http://www.apache.org/licenses/LICENSE-2.0 2470 * 2471 * Unless required by applicable law or agreed to in writing, software 2472 * distributed under the License is distributed on an "AS IS" BASIS, 2473 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2474 * 2475 * See the License for the specific language governing permissions and 2476 * limitations under the License. 2477 */ 2478 2479 var MathUtils = {}; 2480 2481 /** 2482 * Return the sign of the given number. If the sign is negative, this function 2483 * returns -1. If the sign is positive or zero, this function returns 1. 2484 * @static 2485 * @param {number} num the number to test 2486 * @return {number} -1 if the number is negative, and 1 otherwise 2487 */ 2488 MathUtils.signum = function (num) { 2489 var n = num; 2490 if (typeof(num) === 'string') { 2491 n = parseInt(num, 10); 2492 } else if (typeof(num) !== 'number') { 2493 return 1; 2494 } 2495 return (n < 0) ? -1 : 1; 2496 }; 2497 2498 /** 2499 * @static 2500 * @protected 2501 * @param {number} num number to round 2502 * @return {number} rounded number 2503 */ 2504 MathUtils.floor = function (num) { 2505 return Math.floor(num); 2506 }; 2507 2508 /** 2509 * @static 2510 * @protected 2511 * @param {number} num number to round 2512 * @return {number} rounded number 2513 */ 2514 MathUtils.ceiling = function (num) { 2515 return Math.ceil(num); 2516 }; 2517 2518 /** 2519 * @static 2520 * @protected 2521 * @param {number} num number to round 2522 * @return {number} rounded number 2523 */ 2524 MathUtils.down = function (num) { 2525 return (num < 0) ? Math.ceil(num) : Math.floor(num); 2526 }; 2527 2528 /** 2529 * @static 2530 * @protected 2531 * @param {number} num number to round 2532 * @return {number} rounded number 2533 */ 2534 MathUtils.up = function (num) { 2535 return (num < 0) ? Math.floor(num) : Math.ceil(num); 2536 }; 2537 2538 /** 2539 * @static 2540 * @protected 2541 * @param {number} num number to round 2542 * @return {number} rounded number 2543 */ 2544 MathUtils.halfup = function (num) { 2545 return (num < 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 2546 }; 2547 2548 /** 2549 * @static 2550 * @protected 2551 * @param {number} num number to round 2552 * @return {number} rounded number 2553 */ 2554 MathUtils.halfdown = function (num) { 2555 return (num < 0) ? Math.floor(num + 0.5) : Math.ceil(num - 0.5); 2556 }; 2557 2558 /** 2559 * @static 2560 * @protected 2561 * @param {number} num number to round 2562 * @return {number} rounded number 2563 */ 2564 MathUtils.halfeven = function (num) { 2565 return (Math.floor(num) % 2 === 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 2566 }; 2567 2568 /** 2569 * @static 2570 * @protected 2571 * @param {number} num number to round 2572 * @return {number} rounded number 2573 */ 2574 MathUtils.halfodd = function (num) { 2575 return (Math.floor(num) % 2 !== 0) ? Math.ceil(num - 0.5) : Math.floor(num + 0.5); 2576 }; 2577 2578 /** 2579 * Do a proper modulo function. The Javascript % operator will give the truncated 2580 * division algorithm, but for calendrical calculations, we need the Euclidean 2581 * division algorithm where the remainder of any division, whether the dividend 2582 * is negative or not, is always a positive number in the range [0, modulus).<p> 2583 * 2584 * 2585 * @static 2586 * @param {number} dividend the number being divided 2587 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 2588 * @return the remainder of dividing the dividend by the modulus. 2589 */ 2590 MathUtils.mod = function (dividend, modulus) { 2591 if (modulus == 0) { 2592 return 0; 2593 } 2594 var x = dividend % modulus; 2595 return (x < 0) ? x + modulus : x; 2596 }; 2597 2598 /** 2599 * Do a proper adjusted modulo function. The Javascript % operator will give the truncated 2600 * division algorithm, but for calendrical calculations, we need the Euclidean 2601 * division algorithm where the remainder of any division, whether the dividend 2602 * is negative or not, is always a positive number in the range (0, modulus]. The adjusted 2603 * modulo function differs from the regular modulo function in that when the remainder is 2604 * zero, the modulus should be returned instead.<p> 2605 * 2606 * 2607 * @static 2608 * @param {number} dividend the number being divided 2609 * @param {number} modulus the number dividing the dividend. This should always be a positive number. 2610 * @return the remainder of dividing the dividend by the modulus. 2611 */ 2612 MathUtils.amod = function (dividend, modulus) { 2613 if (modulus == 0) { 2614 return 0; 2615 } 2616 var x = dividend % modulus; 2617 return (x <= 0) ? x + modulus : x; 2618 }; 2619 2620 /** 2621 * Return the number with the decimal shifted by the given precision. 2622 * Positive precisions shift the decimal to the right giving larger 2623 * numbers, and negative ones shift the decimal to the left giving 2624 * smaller numbers. 2625 * 2626 * @static 2627 * @param {number} number the number to shift 2628 * @param {number} precision the number of places to move the decimal point 2629 * @returns {number} the number with the decimal point shifted by the 2630 * given number of decimals 2631 */ 2632 MathUtils.shiftDecimal = function shift(number, precision) { 2633 var numArray = ("" + number).split("e"); 2634 return +(numArray[0] + "e" + (numArray[1] ? (+numArray[1] + precision) : precision)); 2635 }; 2636 2637 /** 2638 * Returns the base 10 logarithm of a number. For platforms that support 2639 * Math.log10() it is used directly. For plaforms that do not, such as Qt/QML, 2640 * it will be calculated using the natural logarithm. 2641 * 2642 * @param {number} num the number to take the logarithm of 2643 * @returns {number} the base-10 logarithm of the given number 2644 */ 2645 MathUtils.log10 = function(num) { 2646 if (typeof(Math.log10) === "function") { 2647 return Math.log10(num); 2648 } 2649 2650 return Math.log(num) / Math.LN10; 2651 }; 2652 2653 /** 2654 * Return the given number with only the given number of significant digits. 2655 * The number of significant digits can start with the digits greater than 2656 * 1 and straddle the decimal point, or it may start after the decimal point. 2657 * If the number of digits requested is less than 1, the original number 2658 * will be returned unchanged. 2659 * 2660 * @static 2661 * @param {number} number the number to return with only significant digits 2662 * @param {number} digits the number of significant digits to include in the 2663 * returned number 2664 * @param {function(number): number=} round a rounding function to use 2665 * @returns {number} the given number with only the requested number of 2666 * significant digits 2667 */ 2668 MathUtils.significant = function(number, digits, round) { 2669 if (digits < 1 || number === 0) return number; 2670 var rnd = round || Math.round; 2671 var factor = -Math.floor(MathUtils.log10(Math.abs(number))) + digits - 1; 2672 return MathUtils.shiftDecimal(rnd(MathUtils.shiftDecimal(number, factor)), -factor); 2673 }; 2674 2675 2676 2677 /*< IString.js */ 2678 /* 2679 * IString.js - ilib string subclass definition 2680 * 2681 * Copyright © 2012-2015, 2018, JEDLSoft 2682 * 2683 * Licensed under the Apache License, Version 2.0 (the "License"); 2684 * you may not use this file except in compliance with the License. 2685 * You may obtain a copy of the License at 2686 * 2687 * http://www.apache.org/licenses/LICENSE-2.0 2688 * 2689 * Unless required by applicable law or agreed to in writing, software 2690 * distributed under the License is distributed on an "AS IS" BASIS, 2691 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 2692 * 2693 * See the License for the specific language governing permissions and 2694 * limitations under the License. 2695 */ 2696 2697 // !depends ilib.js Utils.js Locale.js MathUtils.js 2698 2699 // !data plurals 2700 2701 2702 /** 2703 * @class 2704 * Create a new ilib string instance. This string inherits from and 2705 * extends the Javascript String class. It can be 2706 * used almost anywhere that a normal Javascript string is used, though in 2707 * some instances you will need to call the {@link #toString} method when 2708 * a built-in Javascript string is needed. The formatting methods are 2709 * methods that are not in the intrinsic String class and are most useful 2710 * when localizing strings in an app or web site in combination with 2711 * the ResBundle class.<p> 2712 * 2713 * This class is named IString ("ilib string") so as not to conflict with the 2714 * built-in Javascript String class. 2715 * 2716 * @constructor 2717 * @param {string|IString=} string initialize this instance with this string 2718 */ 2719 var IString = function (string) { 2720 if (typeof(string) === 'object') { 2721 if (string instanceof IString) { 2722 this.str = string.str; 2723 } else { 2724 this.str = string.toString(); 2725 } 2726 } else if (typeof(string) === 'string') { 2727 this.str = new String(string); 2728 } else { 2729 this.str = ""; 2730 } 2731 this.length = this.str.length; 2732 this.cpLength = -1; 2733 this.localeSpec = ilib.getLocale(); 2734 }; 2735 2736 /** 2737 * Return true if the given character is a Unicode surrogate character, 2738 * either high or low. 2739 * 2740 * @private 2741 * @static 2742 * @param {string} ch character to check 2743 * @return {boolean} true if the character is a surrogate 2744 */ 2745 IString._isSurrogate = function (ch) { 2746 var n = ch.charCodeAt(0); 2747 return ((n >= 0xDC00 && n <= 0xDFFF) || (n >= 0xD800 && n <= 0xDBFF)); 2748 }; 2749 2750 // build in the English rule 2751 IString.plurals_default = { 2752 "one": { 2753 "and": [ 2754 { 2755 "eq": [ 2756 "i", 2757 1 2758 ] 2759 }, 2760 { 2761 "eq": [ 2762 "v", 2763 0 2764 ] 2765 } 2766 ] 2767 } 2768 }; 2769 2770 /** 2771 * Convert a UCS-4 code point to a Javascript string. The codepoint can be any valid 2772 * UCS-4 Unicode character, including supplementary characters. Standard Javascript 2773 * only supports supplementary characters using the UTF-16 encoding, which has 2774 * values in the range 0x0000-0xFFFF. String.fromCharCode() will only 2775 * give you a string containing 16-bit characters, and will not properly convert 2776 * the code point for a supplementary character (which has a value > 0xFFFF) into 2777 * two UTF-16 surrogate characters. Instead, it will just just give you whatever 2778 * single character happens to be the same as your code point modulo 0x10000, which 2779 * is almost never what you want.<p> 2780 * 2781 * Similarly, that means if you use String.charCodeAt() 2782 * you will only retrieve a 16-bit value, which may possibly be a single 2783 * surrogate character that is part of a surrogate pair representing a character 2784 * in the supplementary plane. It will not give you a code point. Use 2785 * IString.codePointAt() to access code points in a string, or use 2786 * an iterator to walk through the code points in a string. 2787 * 2788 * @static 2789 * @param {number} codepoint UCS-4 code point to convert to a character 2790 * @return {string} a string containing the character represented by the codepoint 2791 */ 2792 IString.fromCodePoint = function (codepoint) { 2793 if (codepoint < 0x10000) { 2794 return String.fromCharCode(codepoint); 2795 } else { 2796 var high = Math.floor(codepoint / 0x10000) - 1; 2797 var low = codepoint & 0xFFFF; 2798 2799 return String.fromCharCode(0xD800 | ((high & 0x000F) << 6) | ((low & 0xFC00) >> 10)) + 2800 String.fromCharCode(0xDC00 | (low & 0x3FF)); 2801 } 2802 }; 2803 2804 /** 2805 * Convert the character or the surrogate pair at the given 2806 * index into the intrinsic Javascript string to a Unicode 2807 * UCS-4 code point. 2808 * 2809 * @static 2810 * @param {string} str string to get the code point from 2811 * @param {number} index index into the string 2812 * @return {number} code point of the character at the 2813 * given index into the string 2814 */ 2815 IString.toCodePoint = function(str, index) { 2816 if (!str || str.length === 0) { 2817 return -1; 2818 } 2819 var code = -1, high = str.charCodeAt(index); 2820 if (high >= 0xD800 && high <= 0xDBFF) { 2821 if (str.length > index+1) { 2822 var low = str.charCodeAt(index+1); 2823 if (low >= 0xDC00 && low <= 0xDFFF) { 2824 code = (((high & 0x3C0) >> 6) + 1) << 16 | 2825 (((high & 0x3F) << 10) | (low & 0x3FF)); 2826 } 2827 } 2828 } else { 2829 code = high; 2830 } 2831 2832 return code; 2833 }; 2834 2835 /** 2836 * Load the plural the definitions of plurals for the locale. 2837 * @param {boolean=} sync 2838 * @param {Locale|string=} locale 2839 * @param {Object=} loadParams 2840 * @param {function(*)=} onLoad 2841 */ 2842 IString.loadPlurals = function (sync, locale, loadParams, onLoad) { 2843 var loc; 2844 if (locale) { 2845 loc = (typeof(locale) === 'string') ? new Locale(locale) : locale; 2846 } else { 2847 loc = new Locale(ilib.getLocale()); 2848 } 2849 var spec = loc.getLanguage(); 2850 if (!ilib.data["plurals_" + spec]) { 2851 Utils.loadData({ 2852 name: "plurals.json", 2853 object: "IString", 2854 locale: loc, 2855 sync: sync, 2856 loadParams: loadParams, 2857 callback: ilib.bind(this, function(plurals) { 2858 if (!plurals) { 2859 ilib.data.cache.IString[spec] = IString.plurals_default; 2860 } 2861 ilib.data["plurals_" + spec] = plurals || IString.plurals_default; 2862 if (onLoad && typeof(onLoad) === 'function') { 2863 onLoad(ilib.data["plurals_" + spec]); 2864 } 2865 }) 2866 }); 2867 } else { 2868 if (onLoad && typeof(onLoad) === 'function') { 2869 onLoad(ilib.data["plurals_" + spec]); 2870 } 2871 } 2872 }; 2873 2874 /** 2875 * @private 2876 * @static 2877 */ 2878 IString._fncs = { 2879 /** 2880 * @private 2881 * @param {Object} obj 2882 * @return {string|undefined} 2883 */ 2884 firstProp: function (obj) { 2885 for (var p in obj) { 2886 if (p && obj[p]) { 2887 return p; 2888 } 2889 } 2890 return undefined; // should never get here 2891 }, 2892 2893 /** 2894 * @private 2895 * @param {Object} obj 2896 * @return {string|undefined} 2897 */ 2898 firstPropRule: function (obj) { 2899 if (Object.prototype.toString.call(obj) === '[object Array]') { 2900 return "inrange"; 2901 } else if (Object.prototype.toString.call(obj) === '[object Object]') { 2902 for (var p in obj) { 2903 if (p && obj[p]) { 2904 return p; 2905 } 2906 } 2907 2908 } 2909 return undefined; // should never get here 2910 }, 2911 2912 /** 2913 * @private 2914 * @param {Object} obj 2915 * @param {number|Object} n 2916 * @return {?} 2917 */ 2918 getValue: function (obj, n) { 2919 if (typeof(obj) === 'object') { 2920 var subrule = IString._fncs.firstPropRule(obj); 2921 if (subrule === "inrange") { 2922 return IString._fncs[subrule](obj, n); 2923 } 2924 return IString._fncs[subrule](obj[subrule], n); 2925 } else if (typeof(obj) === 'string') { 2926 if (typeof(n) === 'object'){ 2927 return n[obj]; 2928 } 2929 return n; 2930 } else { 2931 return obj; 2932 } 2933 }, 2934 2935 /** 2936 * @private 2937 * @param {number|Object} n 2938 * @param {Array.<number|Array.<number>>|Object} range 2939 * @return {boolean} 2940 */ 2941 matchRangeContinuous: function(n, range) { 2942 2943 for (var num in range) { 2944 if (typeof(num) !== 'undefined' && typeof(range[num]) !== 'undefined') { 2945 var obj = range[num]; 2946 if (typeof(obj) === 'number') { 2947 if (n === range[num]) { 2948 return true; 2949 } else if (n >= range[0] && n <= range[1]) { 2950 return true; 2951 } 2952 } else if (Object.prototype.toString.call(obj) === '[object Array]') { 2953 if (n >= obj[0] && n <= obj[1]) { 2954 return true; 2955 } 2956 } 2957 } 2958 } 2959 return false; 2960 }, 2961 2962 /** 2963 * @private 2964 * @param {*} number 2965 * @return {Object} 2966 */ 2967 calculateNumberDigits: function(number) { 2968 var numberToString = number.toString(); 2969 var parts = []; 2970 var numberDigits = {}; 2971 var operandSymbol = {}; 2972 var integerPart, decimalPartLength, decimalPart; 2973 2974 if (numberToString.indexOf('.') !== -1) { //decimal 2975 parts = numberToString.split('.', 2); 2976 numberDigits.integerPart = parseInt(parts[0], 10); 2977 numberDigits.decimalPartLength = parts[1].length; 2978 numberDigits.decimalPart = parseInt(parts[1], 10); 2979 2980 operandSymbol.n = parseFloat(number); 2981 operandSymbol.i = numberDigits.integerPart; 2982 operandSymbol.v = numberDigits.decimalPartLength; 2983 operandSymbol.w = numberDigits.decimalPartLength; 2984 operandSymbol.f = numberDigits.decimalPart; 2985 operandSymbol.t = numberDigits.decimalPart; 2986 2987 } else { 2988 numberDigits.integerPart = number; 2989 numberDigits.decimalPartLength = 0; 2990 numberDigits.decimalPart = 0; 2991 2992 operandSymbol.n = parseInt(number, 10); 2993 operandSymbol.i = numberDigits.integerPart; 2994 operandSymbol.v = 0; 2995 operandSymbol.w = 0; 2996 operandSymbol.f = 0; 2997 operandSymbol.t = 0; 2998 2999 } 3000 return operandSymbol 3001 }, 3002 3003 /** 3004 * @private 3005 * @param {number|Object} n 3006 * @param {Array.<number|Array.<number>>|Object} range 3007 * @return {boolean} 3008 */ 3009 matchRange: function(n, range) { 3010 return IString._fncs.matchRangeContinuous(n, range); 3011 }, 3012 3013 /** 3014 * @private 3015 * @param {Object} rule 3016 * @param {number} n 3017 * @return {boolean} 3018 */ 3019 is: function(rule, n) { 3020 var left = IString._fncs.getValue(rule[0], n); 3021 var right = IString._fncs.getValue(rule[1], n); 3022 return left == right; 3023 }, 3024 3025 /** 3026 * @private 3027 * @param {Object} rule 3028 * @param {number} n 3029 * @return {boolean} 3030 */ 3031 isnot: function(rule, n) { 3032 return IString._fncs.getValue(rule[0], n) != IString._fncs.getValue(rule[1], n); 3033 }, 3034 3035 /** 3036 * @private 3037 * @param {Object} rule 3038 * @param {number|Object} n 3039 * @return {boolean} 3040 */ 3041 inrange: function(rule, n) { 3042 if (typeof(rule[0]) === 'number') { 3043 if(typeof(n) === 'object') { 3044 return IString._fncs.matchRange(n.n,rule); 3045 } 3046 return IString._fncs.matchRange(n,rule); 3047 } else if (typeof(rule[0]) === 'undefined') { 3048 var subrule = IString._fncs.firstPropRule(rule); 3049 return IString._fncs[subrule](rule[subrule], n); 3050 } else { 3051 return IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3052 } 3053 }, 3054 /** 3055 * @private 3056 * @param {Object} rule 3057 * @param {number} n 3058 * @return {boolean} 3059 */ 3060 notin: function(rule, n) { 3061 return !IString._fncs.matchRange(IString._fncs.getValue(rule[0], n), rule[1]); 3062 }, 3063 3064 /** 3065 * @private 3066 * @param {Object} rule 3067 * @param {number} n 3068 * @return {boolean} 3069 */ 3070 within: function(rule, n) { 3071 return IString._fncs.matchRangeContinuous(IString._fncs.getValue(rule[0], n), rule[1]); 3072 }, 3073 3074 /** 3075 * @private 3076 * @param {Object} rule 3077 * @param {number} n 3078 * @return {number} 3079 */ 3080 mod: function(rule, n) { 3081 return MathUtils.mod(IString._fncs.getValue(rule[0], n), IString._fncs.getValue(rule[1], n)); 3082 }, 3083 3084 /** 3085 * @private 3086 * @param {Object} rule 3087 * @param {number} n 3088 * @return {number} 3089 */ 3090 n: function(rule, n) { 3091 return n; 3092 }, 3093 3094 /** 3095 * @private 3096 * @param {Object} rule 3097 * @param {number|Object} n 3098 * @return {boolean} 3099 */ 3100 or: function(rule, n) { 3101 var ruleLength = rule.length; 3102 var result, i; 3103 for (i=0; i < ruleLength; i++) { 3104 result = IString._fncs.getValue(rule[i], n); 3105 if (result) { 3106 return true; 3107 } 3108 } 3109 return false; 3110 }, 3111 /** 3112 * @private 3113 * @param {Object} rule 3114 * @param {number|Object} n 3115 * @return {boolean} 3116 */ 3117 and: function(rule, n) { 3118 var ruleLength = rule.length; 3119 var result, i; 3120 for (i=0; i < ruleLength; i++) { 3121 result= IString._fncs.getValue(rule[i], n); 3122 if (!result) { 3123 return false; 3124 } 3125 } 3126 return true; 3127 }, 3128 /** 3129 * @private 3130 * @param {Object} rule 3131 * @param {number|Object} n 3132 * @return {boolean} 3133 */ 3134 eq: function(rule, n) { 3135 var valueLeft = IString._fncs.getValue(rule[0], n); 3136 var valueRight; 3137 3138 if (typeof(rule[0]) === 'string') { 3139 if (typeof(n) === 'object'){ 3140 valueRight = n[rule[0]]; 3141 if (typeof(rule[1])=== 'number'){ 3142 valueRight = IString._fncs.getValue(rule[1], n); 3143 } else if (typeof(rule[1])=== 'object' && (IString._fncs.firstPropRule(rule[1]) === "inrange" )){ 3144 valueRight = IString._fncs.getValue(rule[1], n); 3145 } 3146 } 3147 } else { 3148 if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod 3149 valueRight = IString._fncs.getValue(rule[1], valueLeft); 3150 } else { 3151 valueRight = IString._fncs.getValue(rule[1], n); 3152 } 3153 } 3154 if(typeof(valueRight) === 'boolean') { 3155 return (valueRight ? true : false); 3156 } else { 3157 return (valueLeft == valueRight ? true :false); 3158 } 3159 }, 3160 /** 3161 * @private 3162 * @param {Object} rule 3163 * @param {number|Object} n 3164 * @return {boolean} 3165 */ 3166 neq: function(rule, n) { 3167 var valueLeft = IString._fncs.getValue(rule[0], n); 3168 var valueRight; 3169 var leftRange; 3170 var rightRange; 3171 3172 if (typeof(rule[0]) === 'string') { 3173 valueRight = n[rule[0]]; 3174 if (typeof(rule[1])=== 'number'){ 3175 valueRight = IString._fncs.getValue(rule[1], n); 3176 } else if (typeof(rule[1]) === 'object') { 3177 leftRange = rule[1][0]; 3178 rightRange = rule[1][1]; 3179 if (typeof(leftRange) === 'number' && 3180 typeof(rightRange) === 'number'){ 3181 3182 if (valueLeft >= leftRange && valueRight <= rightRange) { 3183 return false 3184 } else { 3185 return true; 3186 } 3187 } 3188 } 3189 } else { 3190 if (IString._fncs.firstPropRule(rule[1]) === "inrange") { // mod 3191 valueRight = IString._fncs.getValue(rule[1], valueLeft); 3192 } else { 3193 valueRight = IString._fncs.getValue(rule[1], n); 3194 } 3195 } 3196 3197 if(typeof(valueRight) === 'boolean') {//mod 3198 return (valueRight? false : true); 3199 } else { 3200 return (valueLeft !== valueRight ? true :false); 3201 } 3202 3203 } 3204 }; 3205 3206 IString.prototype = { 3207 /** 3208 * Return the length of this string in characters. This function defers to the regular 3209 * Javascript string class in order to perform the length function. Please note that this 3210 * method is a real method, whereas the length property of Javascript strings is 3211 * implemented by native code and appears as a property.<p> 3212 * 3213 * Example: 3214 * 3215 * <pre> 3216 * var str = new IString("this is a string"); 3217 * console.log("String is " + str._length() + " characters long."); 3218 * </pre> 3219 * @private 3220 */ 3221 _length: function () { 3222 return this.str.length; 3223 }, 3224 3225 /** 3226 * Format this string instance as a message, replacing the parameters with 3227 * the given values.<p> 3228 * 3229 * The string can contain any text that a regular Javascript string can 3230 * contain. Replacement parameters have the syntax: 3231 * 3232 * <pre> 3233 * {name} 3234 * </pre> 3235 * 3236 * Where "name" can be any string surrounded by curly brackets. The value of 3237 * "name" is taken from the parameters argument.<p> 3238 * 3239 * Example: 3240 * 3241 * <pre> 3242 * var str = new IString("There are {num} objects."); 3243 * console.log(str.format({ 3244 * num: 12 3245 * }); 3246 * </pre> 3247 * 3248 * Would give the output: 3249 * 3250 * <pre> 3251 * There are 12 objects. 3252 * </pre> 3253 * 3254 * If a property is missing from the parameter block, the replacement 3255 * parameter substring is left untouched in the string, and a different 3256 * set of parameters may be applied a second time. This way, different 3257 * parts of the code may format different parts of the message that they 3258 * happen to know about.<p> 3259 * 3260 * Example: 3261 * 3262 * <pre> 3263 * var str = new IString("There are {num} objects in the {container}."); 3264 * console.log(str.format({ 3265 * num: 12 3266 * }); 3267 * </pre> 3268 * 3269 * Would give the output:<p> 3270 * 3271 * <pre> 3272 * There are 12 objects in the {container}. 3273 * </pre> 3274 * 3275 * The result can then be formatted again with a different parameter block that 3276 * specifies a value for the container property. 3277 * 3278 * @param params a Javascript object containing values for the replacement 3279 * parameters in the current string 3280 * @return a new IString instance with as many replacement parameters filled 3281 * out as possible with real values. 3282 */ 3283 format: function (params) { 3284 var formatted = this.str; 3285 if (params) { 3286 var regex; 3287 for (var p in params) { 3288 if (typeof(params[p]) !== 'undefined') { 3289 regex = new RegExp("\{"+p+"\}", "g"); 3290 formatted = formatted.replace(regex, params[p]); 3291 } 3292 } 3293 } 3294 return formatted.toString(); 3295 }, 3296 3297 /** @private */ 3298 _testChoice: function(index, limit) { 3299 var numberDigits = {}; 3300 var operandValue = {}; 3301 3302 switch (typeof(index)) { 3303 case 'number': 3304 operandValue = IString._fncs.calculateNumberDigits(index); 3305 3306 if (limit.substring(0,2) === "<=") { 3307 limit = parseFloat(limit.substring(2)); 3308 return operandValue.n <= limit; 3309 } else if (limit.substring(0,2) === ">=") { 3310 limit = parseFloat(limit.substring(2)); 3311 return operandValue.n >= limit; 3312 } else if (limit.charAt(0) === "<") { 3313 limit = parseFloat(limit.substring(1)); 3314 return operandValue.n < limit; 3315 } else if (limit.charAt(0) === ">") { 3316 limit = parseFloat(limit.substring(1)); 3317 return operandValue.n > limit; 3318 } else { 3319 this.locale = this.locale || new Locale(this.localeSpec); 3320 switch (limit) { 3321 case "zero": 3322 case "one": 3323 case "two": 3324 case "few": 3325 case "many": 3326 // CLDR locale-dependent number classes 3327 var ruleset = ilib.data["plurals_" + this.locale.getLanguage()]; 3328 if (ruleset) { 3329 var rule = ruleset[limit]; 3330 return IString._fncs.getValue(rule, operandValue); 3331 } 3332 break; 3333 case "": 3334 case "other": 3335 // matches anything 3336 return true; 3337 default: 3338 var dash = limit.indexOf("-"); 3339 if (dash !== -1) { 3340 // range 3341 var start = limit.substring(0, dash); 3342 var end = limit.substring(dash+1); 3343 return operandValue.n >= parseInt(start, 10) && operandValue.n <= parseInt(end, 10); 3344 } else { 3345 return operandValue.n === parseInt(limit, 10); 3346 } 3347 } 3348 } 3349 break; 3350 case 'boolean': 3351 return (limit === "true" && index === true) || (limit === "false" && index === false); 3352 3353 case 'string': 3354 var regexp = new RegExp(limit, "i"); 3355 return regexp.test(index); 3356 3357 case 'object': 3358 throw "syntax error: formatChoice parameter for the argument index cannot be an object"; 3359 } 3360 3361 return false; 3362 }, 3363 3364 /** 3365 * Format a string as one of a choice of strings dependent on the value of 3366 * a particular argument index or array of indices.<p> 3367 * 3368 * The syntax of the choice string is as follows. The string contains a 3369 * series of choices separated by a vertical bar character "|". Each choice 3370 * has a value or range of values to match followed by a hash character "#" 3371 * followed by the string to use if the variable matches the criteria.<p> 3372 * 3373 * Example string: 3374 * 3375 * <pre> 3376 * var num = 2; 3377 * var str = new IString("0#There are no objects.|1#There is one object.|2#There are {number} objects."); 3378 * console.log(str.formatChoice(num, { 3379 * number: num 3380 * })); 3381 * </pre> 3382 * 3383 * Gives the output: 3384 * 3385 * <pre> 3386 * "There are 2 objects." 3387 * </pre> 3388 * 3389 * The strings to format may contain replacement variables that will be formatted 3390 * using the format() method above and the params argument as a source of values 3391 * to use while formatting those variables.<p> 3392 * 3393 * If the criterion for a particular choice is empty, that choice will be used 3394 * as the default one for use when none of the other choice's criteria match.<p> 3395 * 3396 * Example string: 3397 * 3398 * <pre> 3399 * var num = 22; 3400 * var str = new IString("0#There are no objects.|1#There is one object.|#There are {number} objects."); 3401 * console.log(str.formatChoice(num, { 3402 * number: num 3403 * })); 3404 * </pre> 3405 * 3406 * Gives the output: 3407 * 3408 * <pre> 3409 * "There are 22 objects." 3410 * </pre> 3411 * 3412 * If multiple choice patterns can match a given argument index, the first one 3413 * encountered in the string will be used. If no choice patterns match the 3414 * argument index, then the default choice will be used. If there is no default 3415 * choice defined, then this method will return an empty string.<p> 3416 * 3417 * <b>Special Syntax</b><p> 3418 * 3419 * For any choice format string, all of the patterns in the string should be 3420 * of a single type: numeric, boolean, or string/regexp. The type of the 3421 * patterns is determined by the type of the argument index parameter.<p> 3422 * 3423 * If the argument index is numeric, then some special syntax can be used 3424 * in the patterns to match numeric ranges.<p> 3425 * 3426 * <ul> 3427 * <li><i>>x</i> - match any number that is greater than x 3428 * <li><i>>=x</i> - match any number that is greater than or equal to x 3429 * <li><i><x</i> - match any number that is less than x 3430 * <li><i><=x</i> - match any number that is less than or equal to x 3431 * <li><i>start-end</i> - match any number in the range [start,end) 3432 * <li><i>zero</i> - match any number in the class "zero". (See below for 3433 * a description of number classes.) 3434 * <li><i>one</i> - match any number in the class "one" 3435 * <li><i>two</i> - match any number in the class "two" 3436 * <li><i>few</i> - match any number in the class "few" 3437 * <li><i>many</i> - match any number in the class "many" 3438 * <li><i>other</i> - match any number in the other or default class 3439 * </ul> 3440 * 3441 * A number class defines a set of numbers that receive a particular syntax 3442 * in the strings. For example, in Slovenian, integers ending in the digit 3443 * "1" are in the "one" class, including 1, 21, 31, ... 101, 111, etc. 3444 * Similarly, integers ending in the digit "2" are in the "two" class. 3445 * Integers ending in the digits "3" or "4" are in the "few" class, and 3446 * every other integer is handled by the default string.<p> 3447 * 3448 * The definition of what numbers are included in a class is locale-dependent. 3449 * They are defined in the data file plurals.json. If your string is in a 3450 * different locale than the default for ilib, you should call the setLocale() 3451 * method of the string instance before calling this method.<p> 3452 * 3453 * <b>Other Pattern Types</b><p> 3454 * 3455 * If the argument index is a boolean, the string values "true" and "false" 3456 * may appear as the choice patterns.<p> 3457 * 3458 * If the argument index is of type string, then the choice patterns may contain 3459 * regular expressions, or static strings as degenerate regexps.<p> 3460 * 3461 * <b>Multiple Indexes</b><p> 3462 * 3463 * If you have 2 or more indexes to format into a string, you can pass them as 3464 * an array. When you do that, the patterns to match should be a comma-separate 3465 * list of patterns as per the rules above.<p> 3466 * 3467 * Example string: 3468 * 3469 * <pre> 3470 * var str = new IString("zero,zero#There are no objects on zero pages.|one,one#There is 1 object on 1 page.|other,one#There are {number} objects on 1 page.|#There are {number} objects on {pages} pages."); 3471 * var num = 4, pages = 1; 3472 * console.log(str.formatChoice([num, pages], { 3473 * number: num, 3474 * pages: pages 3475 * })); 3476 * </pre> 3477 * 3478 * Gives the output:<p> 3479 * 3480 * <pre> 3481 * "There are 4 objects on 1 page." 3482 * </pre> 3483 * 3484 * Note that when there is a single index, you would typically leave the pattern blank to 3485 * indicate the default choice. When there are multiple indices, sometimes one of the 3486 * patterns has to be the default case when the other is not. Rather than leaving one or 3487 * more of the patterns blank with commas that look out-of-place in the middle of it, you 3488 * can use the word "other" to indicate a match with the default or other choice. The above example 3489 * shows the use of the "other" pattern. That said, you are allowed to leave the pattern 3490 * blank if you so choose. In the example above, the pattern for the third string could 3491 * easily have been written as ",one" instead of "other,one" and the result will be the same. 3492 * 3493 * @param {*|Array.<*>} argIndex The index into the choice array of the current parameter, 3494 * or an array of indices 3495 * @param {Object} params The hash of parameter values that replace the replacement 3496 * variables in the string 3497 * @throws "syntax error in choice format pattern: " if there is a syntax error 3498 * @return {string} the formatted string 3499 */ 3500 formatChoice: function(argIndex, params) { 3501 var choices = this.str.split("|"); 3502 var limits = []; 3503 var strings = []; 3504 var i; 3505 var parts; 3506 var arg; 3507 var result = undefined; 3508 var defaultCase = ""; 3509 3510 if (this.str.length === 0) { 3511 // nothing to do 3512 return ""; 3513 } 3514 3515 // first parse all the choices 3516 for (i = 0; i < choices.length; i++) { 3517 parts = choices[i].split("#"); 3518 if (parts.length > 2) { 3519 limits[i] = parts[0]; 3520 parts = parts.shift(); 3521 strings[i] = parts.join("#"); 3522 } else if (parts.length === 2) { 3523 limits[i] = parts[0]; 3524 strings[i] = parts[1]; 3525 } else { 3526 // syntax error 3527 throw "syntax error in choice format pattern: " + choices[i]; 3528 } 3529 } 3530 3531 var args = (ilib.isArray(argIndex)) ? argIndex : [argIndex]; 3532 3533 // then apply the argument index (or indices) 3534 for (i = 0; i < limits.length; i++) { 3535 if (limits[i].length === 0) { 3536 // this is default case 3537 defaultCase = new IString(strings[i]); 3538 } else { 3539 var limitsArr = (limits[i].indexOf(",") > -1) ? limits[i].split(",") : [limits[i]]; 3540 3541 var applicable = true; 3542 for (var j = 0; applicable && j < args.length && j < limitsArr.length; j++) { 3543 applicable = this._testChoice(args[j], limitsArr[j]); 3544 } 3545 3546 if (applicable) { 3547 result = new IString(strings[i]); 3548 i = limits.length; 3549 } 3550 } 3551 } 3552 3553 if (!result) { 3554 result = defaultCase || new IString(""); 3555 } 3556 3557 result = result.format(params); 3558 3559 return result.toString(); 3560 }, 3561 3562 // delegates 3563 /** 3564 * Same as String.toString() 3565 * @return {string} this instance as regular Javascript string 3566 */ 3567 toString: function () { 3568 return this.str.toString(); 3569 }, 3570 3571 /** 3572 * Same as String.valueOf() 3573 * @return {string} this instance as a regular Javascript string 3574 */ 3575 valueOf: function () { 3576 return this.str.valueOf(); 3577 }, 3578 3579 /** 3580 * Same as String.charAt() 3581 * @param {number} index the index of the character being sought 3582 * @return {IString} the character at the given index 3583 */ 3584 charAt: function(index) { 3585 return new IString(this.str.charAt(index)); 3586 }, 3587 3588 /** 3589 * Same as String.charCodeAt(). This only reports on 3590 * 2-byte UCS-2 Unicode values, and does not take into 3591 * account supplementary characters encoded in UTF-16. 3592 * If you would like to take account of those characters, 3593 * use codePointAt() instead. 3594 * @param {number} index the index of the character being sought 3595 * @return {number} the character code of the character at the 3596 * given index in the string 3597 */ 3598 charCodeAt: function(index) { 3599 return this.str.charCodeAt(index); 3600 }, 3601 3602 /** 3603 * Same as String.concat() 3604 * @param {string} strings strings to concatenate to the current one 3605 * @return {IString} a concatenation of the given strings 3606 */ 3607 concat: function(strings) { 3608 return new IString(this.str.concat(strings)); 3609 }, 3610 3611 /** 3612 * Same as String.indexOf() 3613 * @param {string} searchValue string to search for 3614 * @param {number} start index into the string to start searching, or 3615 * undefined to search the entire string 3616 * @return {number} index into the string of the string being sought, 3617 * or -1 if the string is not found 3618 */ 3619 indexOf: function(searchValue, start) { 3620 return this.str.indexOf(searchValue, start); 3621 }, 3622 3623 /** 3624 * Same as String.lastIndexOf() 3625 * @param {string} searchValue string to search for 3626 * @param {number} start index into the string to start searching, or 3627 * undefined to search the entire string 3628 * @return {number} index into the string of the string being sought, 3629 * or -1 if the string is not found 3630 */ 3631 lastIndexOf: function(searchValue, start) { 3632 return this.str.lastIndexOf(searchValue, start); 3633 }, 3634 3635 /** 3636 * Same as String.match() 3637 * @param {string} regexp the regular expression to match 3638 * @return {Array.<string>} an array of matches 3639 */ 3640 match: function(regexp) { 3641 return this.str.match(regexp); 3642 }, 3643 3644 /** 3645 * Same as String.replace() 3646 * @param {string} searchValue a regular expression to search for 3647 * @param {string} newValue the string to replace the matches with 3648 * @return {IString} a new string with all the matches replaced 3649 * with the new value 3650 */ 3651 replace: function(searchValue, newValue) { 3652 return new IString(this.str.replace(searchValue, newValue)); 3653 }, 3654 3655 /** 3656 * Same as String.search() 3657 * @param {string} regexp the regular expression to search for 3658 * @return {number} position of the match, or -1 for no match 3659 */ 3660 search: function(regexp) { 3661 return this.str.search(regexp); 3662 }, 3663 3664 /** 3665 * Same as String.slice() 3666 * @param {number} start first character to include in the string 3667 * @param {number} end include all characters up to, but not including 3668 * the end character 3669 * @return {IString} a slice of the current string 3670 */ 3671 slice: function(start, end) { 3672 return new IString(this.str.slice(start, end)); 3673 }, 3674 3675 /** 3676 * Same as String.split() 3677 * @param {string} separator regular expression to match to find 3678 * separations between the parts of the text 3679 * @param {number} limit maximum number of items in the final 3680 * output array. Any items beyond that limit will be ignored. 3681 * @return {Array.<string>} the parts of the current string split 3682 * by the separator 3683 */ 3684 split: function(separator, limit) { 3685 return this.str.split(separator, limit); 3686 }, 3687 3688 /** 3689 * Same as String.substr() 3690 * @param {number} start the index of the character that should 3691 * begin the returned substring 3692 * @param {number} length the number of characters to return after 3693 * the start character. 3694 * @return {IString} the requested substring 3695 */ 3696 substr: function(start, length) { 3697 var plat = ilib._getPlatform(); 3698 if (plat === "qt" || plat === "rhino" || plat === "trireme") { 3699 // qt and rhino have a broken implementation of substr(), so 3700 // work around it 3701 if (typeof(length) === "undefined") { 3702 length = this.str.length - start; 3703 } 3704 } 3705 return new IString(this.str.substr(start, length)); 3706 }, 3707 3708 /** 3709 * Same as String.substring() 3710 * @param {number} from the index of the character that should 3711 * begin the returned substring 3712 * @param {number} to the index where to stop the extraction. If 3713 * omitted, extracts the rest of the string 3714 * @return {IString} the requested substring 3715 */ 3716 substring: function(from, to) { 3717 return this.str.substring(from, to); 3718 }, 3719 3720 /** 3721 * Same as String.toLowerCase(). Note that this method is 3722 * not locale-sensitive. 3723 * @return {IString} a string with the first character 3724 * lower-cased 3725 */ 3726 toLowerCase: function() { 3727 return this.str.toLowerCase(); 3728 }, 3729 3730 /** 3731 * Same as String.toUpperCase(). Note that this method is 3732 * not locale-sensitive. Use toLocaleUpperCase() instead 3733 * to get locale-sensitive behaviour. 3734 * @return {IString} a string with the first character 3735 * upper-cased 3736 */ 3737 toUpperCase: function() { 3738 return this.str.toUpperCase(); 3739 }, 3740 3741 /** 3742 * Convert the character or the surrogate pair at the given 3743 * index into the string to a Unicode UCS-4 code point. 3744 * @protected 3745 * @param {number} index index into the string 3746 * @return {number} code point of the character at the 3747 * given index into the string 3748 */ 3749 _toCodePoint: function (index) { 3750 return IString.toCodePoint(this.str, index); 3751 }, 3752 3753 /** 3754 * Call the callback with each character in the string one at 3755 * a time, taking care to step through the surrogate pairs in 3756 * the UTF-16 encoding properly.<p> 3757 * 3758 * The standard Javascript String's charAt() method only 3759 * returns a particular 16-bit character in the 3760 * UTF-16 encoding scheme. 3761 * If the index to charAt() is pointing to a low- or 3762 * high-surrogate character, 3763 * it will return the surrogate character rather 3764 * than the the character 3765 * in the supplementary planes that the two surrogates together 3766 * encode. This function will call the callback with the full 3767 * character, making sure to join two 3768 * surrogates into one character in the supplementary planes 3769 * where necessary.<p> 3770 * 3771 * @param {function(string)} callback a callback function to call with each 3772 * full character in the current string 3773 */ 3774 forEach: function(callback) { 3775 if (typeof(callback) === 'function') { 3776 var it = this.charIterator(); 3777 while (it.hasNext()) { 3778 callback(it.next()); 3779 } 3780 } 3781 }, 3782 3783 /** 3784 * Call the callback with each numeric code point in the string one at 3785 * a time, taking care to step through the surrogate pairs in 3786 * the UTF-16 encoding properly.<p> 3787 * 3788 * The standard Javascript String's charCodeAt() method only 3789 * returns information about a particular 16-bit character in the 3790 * UTF-16 encoding scheme. 3791 * If the index to charCodeAt() is pointing to a low- or 3792 * high-surrogate character, 3793 * it will return the code point of the surrogate character rather 3794 * than the code point of the character 3795 * in the supplementary planes that the two surrogates together 3796 * encode. This function will call the callback with the full 3797 * code point of each character, making sure to join two 3798 * surrogates into one code point in the supplementary planes.<p> 3799 * 3800 * @param {function(string)} callback a callback function to call with each 3801 * code point in the current string 3802 */ 3803 forEachCodePoint: function(callback) { 3804 if (typeof(callback) === 'function') { 3805 var it = this.iterator(); 3806 while (it.hasNext()) { 3807 callback(it.next()); 3808 } 3809 } 3810 }, 3811 3812 /** 3813 * Return an iterator that will step through all of the characters 3814 * in the string one at a time and return their code points, taking 3815 * care to step through the surrogate pairs in UTF-16 encoding 3816 * properly.<p> 3817 * 3818 * The standard Javascript String's charCodeAt() method only 3819 * returns information about a particular 16-bit character in the 3820 * UTF-16 encoding scheme. 3821 * If the index is pointing to a low- or high-surrogate character, 3822 * it will return a code point of the surrogate character rather 3823 * than the code point of the character 3824 * in the supplementary planes that the two surrogates together 3825 * encode.<p> 3826 * 3827 * The iterator instance returned has two methods, hasNext() which 3828 * returns true if the iterator has more code points to iterate through, 3829 * and next() which returns the next code point as a number.<p> 3830 * 3831 * @return {Object} an iterator 3832 * that iterates through all the code points in the string 3833 */ 3834 iterator: function() { 3835 /** 3836 * @constructor 3837 */ 3838 function _iterator (istring) { 3839 this.index = 0; 3840 this.hasNext = function () { 3841 return (this.index < istring.str.length); 3842 }; 3843 this.next = function () { 3844 if (this.index < istring.str.length) { 3845 var num = istring._toCodePoint(this.index); 3846 this.index += ((num > 0xFFFF) ? 2 : 1); 3847 } else { 3848 num = -1; 3849 } 3850 return num; 3851 }; 3852 }; 3853 return new _iterator(this); 3854 }, 3855 3856 /** 3857 * Return an iterator that will step through all of the characters 3858 * in the string one at a time, taking 3859 * care to step through the surrogate pairs in UTF-16 encoding 3860 * properly.<p> 3861 * 3862 * The standard Javascript String's charAt() method only 3863 * returns information about a particular 16-bit character in the 3864 * UTF-16 encoding scheme. 3865 * If the index is pointing to a low- or high-surrogate character, 3866 * it will return that surrogate character rather 3867 * than the surrogate pair which represents a character 3868 * in the supplementary planes.<p> 3869 * 3870 * The iterator instance returned has two methods, hasNext() which 3871 * returns true if the iterator has more characters to iterate through, 3872 * and next() which returns the next character.<p> 3873 * 3874 * @return {Object} an iterator 3875 * that iterates through all the characters in the string 3876 */ 3877 charIterator: function() { 3878 /** 3879 * @constructor 3880 */ 3881 function _chiterator (istring) { 3882 this.index = 0; 3883 this.hasNext = function () { 3884 return (this.index < istring.str.length); 3885 }; 3886 this.next = function () { 3887 var ch; 3888 if (this.index < istring.str.length) { 3889 ch = istring.str.charAt(this.index); 3890 if (IString._isSurrogate(ch) && 3891 this.index+1 < istring.str.length && 3892 IString._isSurrogate(istring.str.charAt(this.index+1))) { 3893 this.index++; 3894 ch += istring.str.charAt(this.index); 3895 } 3896 this.index++; 3897 } 3898 return ch; 3899 }; 3900 }; 3901 return new _chiterator(this); 3902 }, 3903 3904 /** 3905 * Return the code point at the given index when the string is viewed 3906 * as an array of code points. If the index is beyond the end of the 3907 * array of code points or if the index is negative, -1 is returned. 3908 * @param {number} index index of the code point 3909 * @return {number} code point of the character at the given index into 3910 * the string 3911 */ 3912 codePointAt: function (index) { 3913 if (index < 0) { 3914 return -1; 3915 } 3916 var count, 3917 it = this.iterator(), 3918 ch; 3919 for (count = index; count >= 0 && it.hasNext(); count--) { 3920 ch = it.next(); 3921 } 3922 return (count < 0) ? ch : -1; 3923 }, 3924 3925 /** 3926 * Set the locale to use when processing choice formats. The locale 3927 * affects how number classes are interpretted. In some cultures, 3928 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 3929 * in yet others, "few" maps to "any integer that ends in the digits 3930 * 3 or 4". 3931 * @param {Locale|string} locale locale to use when processing choice 3932 * formats with this string 3933 * @param {boolean=} sync [optional] whether to load the locale data synchronously 3934 * or not 3935 * @param {Object=} loadParams [optional] parameters to pass to the loader function 3936 * @param {function(*)=} onLoad [optional] function to call when the loading is done 3937 */ 3938 setLocale: function (locale, sync, loadParams, onLoad) { 3939 if (typeof(locale) === 'object') { 3940 this.locale = locale; 3941 } else { 3942 this.localeSpec = locale; 3943 this.locale = new Locale(locale); 3944 } 3945 3946 IString.loadPlurals(typeof(sync) !== 'undefined' ? sync : true, this.locale, loadParams, onLoad); 3947 }, 3948 3949 /** 3950 * Return the locale to use when processing choice formats. The locale 3951 * affects how number classes are interpretted. In some cultures, 3952 * the limit "few" maps to "any integer that ends in the digits 2 to 9" and 3953 * in yet others, "few" maps to "any integer that ends in the digits 3954 * 3 or 4". 3955 * @return {string} localespec to use when processing choice 3956 * formats with this string 3957 */ 3958 getLocale: function () { 3959 return (this.locale ? this.locale.getSpec() : this.localeSpec) || ilib.getLocale(); 3960 }, 3961 3962 /** 3963 * Return the number of code points in this string. This may be different 3964 * than the number of characters, as the UTF-16 encoding that Javascript 3965 * uses for its basis returns surrogate pairs separately. Two 2-byte 3966 * surrogate characters together make up one character/code point in 3967 * the supplementary character planes. If your string contains no 3968 * characters in the supplementary planes, this method will return the 3969 * same thing as the length() method. 3970 * @return {number} the number of code points in this string 3971 */ 3972 codePointLength: function () { 3973 if (this.cpLength === -1) { 3974 var it = this.iterator(); 3975 this.cpLength = 0; 3976 while (it.hasNext()) { 3977 this.cpLength++; 3978 it.next(); 3979 }; 3980 } 3981 return this.cpLength; 3982 } 3983 }; 3984 3985 3986 3987 /*< LocaleInfo.js */ 3988 /* 3989 * LocaleInfo.js - Encode locale-specific defaults 3990 * 3991 * Copyright © 2012-2015, JEDLSoft 3992 * 3993 * Licensed under the Apache License, Version 2.0 (the "License"); 3994 * you may not use this file except in compliance with the License. 3995 * You may obtain a copy of the License at 3996 * 3997 * http://www.apache.org/licenses/LICENSE-2.0 3998 * 3999 * Unless required by applicable law or agreed to in writing, software 4000 * distributed under the License is distributed on an "AS IS" BASIS, 4001 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4002 * 4003 * See the License for the specific language governing permissions and 4004 * limitations under the License. 4005 */ 4006 4007 // !depends ilib.js Locale.js Utils.js 4008 4009 // !data localeinfo 4010 4011 4012 /** 4013 * @class 4014 * Create a new locale info instance. Locale info instances give information about 4015 * the default settings for a particular locale. These settings may be overridden 4016 * by various parts of the code, and should be used as a fall-back setting of last 4017 * resort. <p> 4018 * 4019 * The optional options object holds extra parameters if they are necessary. The 4020 * current list of supported options are: 4021 * 4022 * <ul> 4023 * <li><i>onLoad</i> - a callback function to call when the locale info object is fully 4024 * loaded. When the onLoad option is given, the localeinfo object will attempt to 4025 * load any missing locale data using the ilib loader callback. 4026 * When the constructor is done (even if the data is already preassembled), the 4027 * onLoad function is called with the current instance as a parameter, so this 4028 * callback can be used with preassembled or dynamic loading or a mix of the two. 4029 * 4030 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 4031 * asynchronously. If this option is given as "false", then the "onLoad" 4032 * callback must be given, as the instance returned from this constructor will 4033 * not be usable for a while. 4034 * 4035 * <li><i>loadParams</i> - an object containing parameters to pass to the 4036 * loader callback function when locale data is missing. The parameters are not 4037 * interpretted or modified in any way. They are simply passed along. The object 4038 * may contain any property/value pairs as long as the calling code is in 4039 * agreement with the loader callback function as to what those parameters mean. 4040 * </ul> 4041 * 4042 * If this copy of ilib is pre-assembled and all the data is already available, 4043 * or if the data was already previously loaded, then this constructor will call 4044 * the onLoad callback immediately when the initialization is done. 4045 * If the onLoad option is not given, this class will only attempt to load any 4046 * missing locale data synchronously. 4047 * 4048 * 4049 * @constructor 4050 * @see {ilib.setLoaderCallback} for information about registering a loader callback 4051 * function 4052 * @param {Locale|string=} locale the locale for which the info is sought, or undefined for 4053 * @param {Object=} options the locale for which the info is sought, or undefined for 4054 * the current locale 4055 */ 4056 var LocaleInfo = function(locale, options) { 4057 var sync = true, 4058 loadParams = undefined; 4059 4060 /** 4061 @private 4062 @type {{ 4063 calendar:string, 4064 clock:string, 4065 currency:string, 4066 delimiter: {quotationStart:string,quotationEnd:string,alternateQuotationStart:string,alternateQuotationEnd:string}, 4067 firstDayOfWeek:number, 4068 meridiems:string, 4069 numfmt:{ 4070 currencyFormats:{common:string,commonNegative:string,iso:string,isoNegative:string}, 4071 decimalChar:string, 4072 exponential:string, 4073 groupChar:string, 4074 negativenumFmt:string, 4075 negativepctFmt:string, 4076 pctChar:string, 4077 pctFmt:string, 4078 prigroupSize:number, 4079 roundingMode:string, 4080 script:string, 4081 secgroupSize:number, 4082 useNative:boolean 4083 }, 4084 timezone:string, 4085 units:string, 4086 weekendEnd:number, 4087 weekendStart:number, 4088 paperSizes:{regular:string} 4089 }} 4090 */ 4091 this.info = LocaleInfo.defaultInfo; 4092 4093 switch (typeof(locale)) { 4094 case "string": 4095 this.locale = new Locale(locale); 4096 break; 4097 default: 4098 case "undefined": 4099 this.locale = new Locale(); 4100 break; 4101 case "object": 4102 this.locale = locale; 4103 break; 4104 } 4105 4106 if (options) { 4107 if (typeof(options.sync) !== 'undefined') { 4108 sync = !!options.sync; 4109 } 4110 4111 if (typeof(options.loadParams) !== 'undefined') { 4112 loadParams = options.loadParams; 4113 } 4114 } 4115 4116 if (!ilib.data.cache.LocaleInfo) { 4117 ilib.data.cache.LocaleInfo = {}; 4118 } 4119 4120 Utils.loadData({ 4121 object: "LocaleInfo", 4122 locale: this.locale, 4123 name: "localeinfo.json", 4124 sync: sync, 4125 loadParams: loadParams, 4126 callback: ilib.bind(this, function (info) { 4127 if (!info) { 4128 info = LocaleInfo.defaultInfo; 4129 var spec = this.locale.getSpec().replace(/-/g, "_"); 4130 ilib.data.cache.LocaleInfo[spec] = info; 4131 } 4132 this.info = info; 4133 if (options && typeof(options.onLoad) === 'function') { 4134 options.onLoad(this); 4135 } 4136 }) 4137 }); 4138 }; 4139 4140 LocaleInfo.defaultInfo = ilib.data.localeinfo; 4141 LocaleInfo.defaultInfo = LocaleInfo.defaultInfo || { 4142 "calendar": "gregorian", 4143 "clock": "24", 4144 "currency": "USD", 4145 "delimiter": { 4146 "quotationStart": "“", 4147 "quotationEnd": "”", 4148 "alternateQuotationStart": "‘", 4149 "alternateQuotationEnd": "’" 4150 }, 4151 "firstDayOfWeek": 1, 4152 "meridiems": "gregorian", 4153 "numfmt": { 4154 "script": "Latn", 4155 "decimalChar": ".", 4156 "groupChar": ",", 4157 "pctChar": "%", 4158 "exponential": "E", 4159 "prigroupSize": 3, 4160 "currencyFormats": { 4161 "common": "{s} {n}", 4162 "commonNegative": "-{s} {n}", 4163 "iso": "{s} {n}", 4164 "isoNegative": "({s} {n})" 4165 }, 4166 "negativenumFmt": "-{n}", 4167 "pctFmt": "{n}%", 4168 "negativepctFmt": "-{n}%", 4169 "roundingMode": "halfdown", 4170 "secGroupSize": null, 4171 "useNative": false 4172 }, 4173 "paperSizes": { 4174 "regular": "A4" 4175 }, 4176 "timezone": "Etc/UTC", 4177 "units": "metric", 4178 "weekendEnd": 0, 4179 "weekendStart": 6 4180 }; 4181 4182 LocaleInfo.prototype = { 4183 /** 4184 * Return the name of the locale's language in English. 4185 * @returns {string} the name of the locale's language in English 4186 */ 4187 getLanguageName: function () { 4188 return this.info["language.name"]; 4189 }, 4190 4191 /** 4192 * Return the name of the locale's region in English. If the locale 4193 * has no region, this returns undefined. 4194 * 4195 * @returns {string|undefined} the name of the locale's region in English 4196 */ 4197 getRegionName: function () { 4198 return this.info["region.name"]; 4199 }, 4200 4201 /** 4202 * Return whether this locale commonly uses the 12- or the 24-hour clock. 4203 * 4204 * @returns {string} "12" if the locale commonly uses a 12-hour clock, or "24" 4205 * if the locale commonly uses a 24-hour clock. 4206 */ 4207 getClock: function() { 4208 return this.info.clock; 4209 }, 4210 4211 /** 4212 * Return the locale that this info object was created with. 4213 * @returns {Locale} The locale spec of the locale used to construct this info instance 4214 */ 4215 getLocale: function () { 4216 return this.locale; 4217 }, 4218 4219 /** 4220 * Return the name of the measuring system that is commonly used in the given locale. 4221 * Valid values are "uscustomary", "imperial", and "metric". 4222 * 4223 * @returns {string} The name of the measuring system commonly used in the locale 4224 */ 4225 getUnits: function () { 4226 return this.info.units; 4227 }, 4228 4229 /** 4230 * Return the name of the calendar that is commonly used in the given locale. 4231 * 4232 * @returns {string} The name of the calendar commonly used in the locale 4233 */ 4234 getCalendar: function () { 4235 return this.info.calendar; 4236 }, 4237 4238 /** 4239 * Return the day of week that starts weeks in the current locale. Days are still 4240 * numbered the standard way with 0 for Sunday through 6 for Saturday, but calendars 4241 * should be displayed and weeks calculated with the day of week returned from this 4242 * function as the first day of the week. 4243 * 4244 * @returns {number} the day of the week that starts weeks in the current locale. 4245 */ 4246 getFirstDayOfWeek: function () { 4247 return this.info.firstDayOfWeek; 4248 }, 4249 4250 /** 4251 * Return the day of week that starts weekend in the current locale. Days are still 4252 * numbered the standard way with 0 for Sunday through 6 for Saturday. 4253 * 4254 * @returns {number} the day of the week that starts weeks in the current locale. 4255 */ 4256 getWeekEndStart: function () { 4257 return this.info.weekendStart; 4258 }, 4259 4260 /** 4261 * Return the day of week that starts weekend in the current locale. Days are still 4262 * numbered the standard way with 0 for Sunday through 6 for Saturday. 4263 * 4264 * @returns {number} the day of the week that starts weeks in the current locale. 4265 */ 4266 getWeekEndEnd: function () { 4267 return this.info.weekendEnd; 4268 }, 4269 4270 /** 4271 * Return the default time zone for this locale. Many locales span across multiple 4272 * time zones. In this case, the time zone with the largest population is chosen 4273 * to represent the locale. This is obviously not that accurate, but then again, 4274 * this method's return value should only be used as a default anyways. 4275 * @returns {string} the default time zone for this locale. 4276 */ 4277 getTimeZone: function () { 4278 return this.info.timezone; 4279 }, 4280 4281 /** 4282 * Return the decimal separator for formatted numbers in this locale. 4283 * @returns {string} the decimal separator char 4284 */ 4285 getDecimalSeparator: function () { 4286 return this.info.numfmt.decimalChar; 4287 }, 4288 4289 /** 4290 * Return the decimal separator for formatted numbers in this locale for native script. 4291 * @returns {string} the decimal separator char 4292 */ 4293 getNativeDecimalSeparator: function () { 4294 return (this.info.native_numfmt && this.info.native_numfmt.decimalChar) || this.info.numfmt.decimalChar; 4295 }, 4296 4297 /** 4298 * Return the separator character used to separate groups of digits on the 4299 * integer side of the decimal character. 4300 * @returns {string} the grouping separator char 4301 */ 4302 getGroupingSeparator: function () { 4303 return this.info.numfmt.groupChar; 4304 }, 4305 4306 /** 4307 * Return the separator character used to separate groups of digits on the 4308 * integer side of the decimal character for the native script if present other than the default script. 4309 * @returns {string} the grouping separator char 4310 */ 4311 getNativeGroupingSeparator: function () { 4312 return (this.info.native_numfmt && this.info.native_numfmt.groupChar) || this.info.numfmt.groupChar; 4313 }, 4314 4315 /** 4316 * Return the minimum number of digits grouped together on the integer side 4317 * for the first (primary) group. 4318 * In western European cultures, groupings are in 1000s, so the number of digits 4319 * is 3. 4320 * @returns {number} the number of digits in a primary grouping, or 0 for no grouping 4321 */ 4322 getPrimaryGroupingDigits: function () { 4323 return (typeof(this.info.numfmt.prigroupSize) !== 'undefined' && this.info.numfmt.prigroupSize) || 0; 4324 }, 4325 4326 /** 4327 * Return the minimum number of digits grouped together on the integer side 4328 * for the second or more (secondary) group.<p> 4329 * 4330 * In western European cultures, all groupings are by 1000s, so the secondary 4331 * size should be 0 because there is no secondary size. In general, if this 4332 * method returns 0, then all groupings are of the primary size.<p> 4333 * 4334 * For some other cultures, the first grouping (primary) 4335 * is 3 and any subsequent groupings (secondary) are two. So, 100000 would be 4336 * written as: "1,00,000". 4337 * 4338 * @returns {number} the number of digits in a secondary grouping, or 0 for no 4339 * secondary grouping. 4340 */ 4341 getSecondaryGroupingDigits: function () { 4342 return this.info.numfmt.secgroupSize || 0; 4343 }, 4344 4345 /** 4346 * Return the format template used to format percentages in this locale. 4347 * @returns {string} the format template for formatting percentages 4348 */ 4349 getPercentageFormat: function () { 4350 return this.info.numfmt.pctFmt; 4351 }, 4352 4353 /** 4354 * Return the format template used to format percentages in this locale 4355 * with negative amounts. 4356 * @returns {string} the format template for formatting percentages 4357 */ 4358 getNegativePercentageFormat: function () { 4359 return this.info.numfmt.negativepctFmt; 4360 }, 4361 4362 /** 4363 * Return the symbol used for percentages in this locale. 4364 * @returns {string} the symbol used for percentages in this locale 4365 */ 4366 getPercentageSymbol: function () { 4367 return this.info.numfmt.pctChar || "%"; 4368 }, 4369 4370 /** 4371 * Return the symbol used for exponential in this locale. 4372 * @returns {string} the symbol used for exponential in this locale 4373 */ 4374 getExponential: function () { 4375 return this.info.numfmt.exponential; 4376 }, 4377 4378 /** 4379 * Return the symbol used for exponential in this locale for native script. 4380 * @returns {string} the symbol used for exponential in this locale for native script 4381 */ 4382 getNativeExponential: function () { 4383 return (this.info.native_numfmt && this.info.native_numfmt.exponential) || this.info.numfmt.exponential; 4384 }, 4385 4386 /** 4387 * Return the symbol used for percentages in this locale for native script. 4388 * @returns {string} the symbol used for percentages in this locale for native script 4389 */ 4390 getNativePercentageSymbol: function () { 4391 return (this.info.native_numfmt && this.info.native_numfmt.pctChar) || this.info.numfmt.pctChar || "%"; 4392 4393 }, 4394 /** 4395 * Return the format template used to format negative numbers in this locale. 4396 * @returns {string} the format template for formatting negative numbers 4397 */ 4398 getNegativeNumberFormat: function () { 4399 return this.info.numfmt.negativenumFmt; 4400 }, 4401 4402 /** 4403 * Return an object containing the format templates for formatting currencies 4404 * in this locale. The object has a number of properties in it that each are 4405 * a particular style of format. Normally, this contains a "common" and an "iso" 4406 * style, but may contain others in the future. 4407 * @returns {Object} an object containing the format templates for currencies 4408 */ 4409 getCurrencyFormats: function () { 4410 return this.info.numfmt.currencyFormats; 4411 }, 4412 4413 /** 4414 * Return the currency that is legal in the locale, or which is most commonly 4415 * used in regular commerce. 4416 * @returns {string} the ISO 4217 code for the currency of this locale 4417 */ 4418 getCurrency: function () { 4419 return this.info.currency; 4420 }, 4421 4422 /** 4423 * Return a string that describes the style of digits used by this locale. 4424 * Possible return values are: 4425 * <ul> 4426 * <li><i>western</i> - uses the regular western 10-based digits 0 through 9 4427 * <li><i>optional</i> - native 10-based digits exist, but in modern usage, 4428 * this locale most often uses western digits 4429 * <li><i>native</i> - native 10-based native digits exist and are used 4430 * regularly by this locale 4431 * <li><i>custom</i> - uses native digits by default that are not 10-based 4432 * </ul> 4433 * @returns {string} string that describes the style of digits used in this locale 4434 */ 4435 getDigitsStyle: function () { 4436 if (this.info.numfmt && this.info.numfmt.useNative) { 4437 return "native"; 4438 } 4439 if (typeof(this.info.native_numfmt) !== 'undefined') { 4440 return "optional"; 4441 } 4442 return "western"; 4443 }, 4444 4445 /** 4446 * Return the digits of the default script if they are defined. 4447 * If not defined, the default should be the regular "Arabic numerals" 4448 * used in the Latin script. (0-9) 4449 * @returns {string|undefined} the digits used in the default script 4450 */ 4451 getDigits: function () { 4452 return this.info.numfmt.digits; 4453 }, 4454 4455 /** 4456 * Return the digits of the native script if they are defined. 4457 * @returns {string|undefined} the digits used in the default script 4458 */ 4459 getNativeDigits: function () { 4460 return (this.info.numfmt.useNative && this.info.numfmt.digits) || (this.info.native_numfmt && this.info.native_numfmt.digits); 4461 }, 4462 4463 /** 4464 * If this locale typically uses a different type of rounding for numeric 4465 * formatting other than halfdown, especially for currency, then it can be 4466 * specified in the localeinfo. If the locale uses the default, then this 4467 * method returns undefined. The locale's rounding method overrides the 4468 * rounding method for the currency itself, which can sometimes shared 4469 * between various locales so it is less specific. 4470 * @returns {string} the name of the rounding mode typically used in this 4471 * locale, or "halfdown" if the locale does not override the default 4472 */ 4473 getRoundingMode: function () { 4474 return this.info.numfmt.roundingMode; 4475 }, 4476 4477 /** 4478 * Return the default script used to write text in the language of this 4479 * locale. Text for most languages is written in only one script, but there 4480 * are some languages where the text can be written in a number of scripts, 4481 * depending on a variety of things such as the region, ethnicity, religion, 4482 * etc. of the author. This method returns the default script for the 4483 * locale, in which the language is most commonly written.<p> 4484 * 4485 * The script is returned as an ISO 15924 4-letter code. 4486 * 4487 * @returns {string} the ISO 15924 code for the default script used to write 4488 * text in this locale 4489 */ 4490 getDefaultScript: function() { 4491 return (this.info.scripts) ? this.info.scripts[0] : "Latn"; 4492 }, 4493 4494 /** 4495 * Return the script used for the current locale. If the current locale 4496 * explicitly defines a script, then this script is returned. If not, then 4497 * the default script for the locale is returned. 4498 * 4499 * @see LocaleInfo.getDefaultScript 4500 * @returns {string} the ISO 15924 code for the script used to write 4501 * text in this locale 4502 */ 4503 getScript: function() { 4504 return this.locale.getScript() || this.getDefaultScript(); 4505 }, 4506 4507 /** 4508 * Return an array of script codes which are used to write text in the current 4509 * language. Text for most languages is written in only one script, but there 4510 * are some languages where the text can be written in a number of scripts, 4511 * depending on a variety of things such as the region, ethnicity, religion, 4512 * etc. of the author. This method returns an array of script codes in which 4513 * the language is commonly written. 4514 * 4515 * @returns {Array.<string>} an array of ISO 15924 codes for the scripts used 4516 * to write text in this language 4517 */ 4518 getAllScripts: function() { 4519 return this.info.scripts || ["Latn"]; 4520 }, 4521 4522 /** 4523 * Return the default style of meridiems used in this locale. Meridiems are 4524 * times of day like AM/PM. In a few locales with some calendars, for example 4525 * Amharic/Ethiopia using the Ethiopic calendar, the times of day may be 4526 * split into different segments than simple AM/PM as in the Gregorian 4527 * calendar. Only a few locales are like that. For most locales, formatting 4528 * a Gregorian date will use the regular Gregorian AM/PM meridiems. 4529 * 4530 * @returns {string} the default meridiems style used in this locale. Possible 4531 * values are "gregorian", "chinese", and "ethiopic" 4532 */ 4533 getMeridiemsStyle: function () { 4534 return this.info.meridiems || "gregorian"; 4535 }, 4536 /** 4537 * Return the default PaperSize information in this locale. 4538 * @returns {string} default PaperSize in this locale 4539 */ 4540 getPaperSize: function () { 4541 return this.info.paperSizes.regular; 4542 }, 4543 /** 4544 * Return the default Delimiter QuotationStart information in this locale. 4545 * @returns {string} default QuotationStart in this locale 4546 */ 4547 getDelimiterQuotationStart: function () { 4548 return this.info.delimiter.quotationStart; 4549 }, 4550 /** 4551 * Return the default Delimiter QuotationEnd information in this locale. 4552 * @returns {string} default QuotationEnd in this locale 4553 */ 4554 getDelimiterQuotationEnd: function () { 4555 return this.info.delimiter.quotationEnd; 4556 } 4557 }; 4558 4559 4560 4561 /*< Calendar.js */ 4562 /* 4563 * Calendar.js - Represent a calendar object. 4564 * 4565 * Copyright © 2012-2015, JEDLSoft 4566 * 4567 * Licensed under the Apache License, Version 2.0 (the "License"); 4568 * you may not use this file except in compliance with the License. 4569 * You may obtain a copy of the License at 4570 * 4571 * http://www.apache.org/licenses/LICENSE-2.0 4572 * 4573 * Unless required by applicable law or agreed to in writing, software 4574 * distributed under the License is distributed on an "AS IS" BASIS, 4575 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4576 * 4577 * See the License for the specific language governing permissions and 4578 * limitations under the License. 4579 */ 4580 4581 /** 4582 * @class 4583 * Superclass for all calendar subclasses that contains shared 4584 * functionality. This class is never instantiated on its own. Instead, 4585 * you should use the {@link CalendarFactory} function to manufacture a new 4586 * instance of a subclass of Calendar. 4587 * 4588 * @private 4589 * @constructor 4590 */ 4591 var Calendar = function() { 4592 }; 4593 4594 /* place for the subclasses to put their constructors so that the factory method 4595 * can find them. Do this to add your calendar after it's defined: 4596 * Calendar._constructors["mytype"] = Calendar.MyTypeConstructor; 4597 */ 4598 Calendar._constructors = {}; 4599 4600 Calendar.prototype = { 4601 /** 4602 * Return the type of this calendar. 4603 * 4604 * @return {string} the name of the type of this calendar 4605 */ 4606 getType: function() { 4607 throw "Cannot call methods of abstract class Calendar"; 4608 }, 4609 4610 /** 4611 * Return the number of months in the given year. The number of months in a year varies 4612 * for some luni-solar calendars because in some years, an extra month is needed to extend the 4613 * days in a year to an entire solar year. The month is represented as a 1-based number 4614 * where 1=first month, 2=second month, etc. 4615 * 4616 * @param {number} year a year for which the number of months is sought 4617 * @return {number} The number of months in the given year 4618 */ 4619 getNumMonths: function(year) { 4620 throw "Cannot call methods of abstract class Calendar"; 4621 }, 4622 4623 /** 4624 * Return the number of days in a particular month in a particular year. This function 4625 * can return a different number for a month depending on the year because of things 4626 * like leap years. 4627 * 4628 * @param {number} month the month for which the length is sought 4629 * @param {number} year the year within which that month can be found 4630 * @return {number} the number of days within the given month in the given year 4631 */ 4632 getMonLength: function(month, year) { 4633 throw "Cannot call methods of abstract class Calendar"; 4634 }, 4635 4636 /** 4637 * Return true if the given year is a leap year in this calendar. 4638 * The year parameter may be given as a number. 4639 * 4640 * @param {number} year the year for which the leap year information is being sought 4641 * @return {boolean} true if the given year is a leap year 4642 */ 4643 isLeapYear: function(year) { 4644 throw "Cannot call methods of abstract class Calendar"; 4645 } 4646 }; 4647 4648 4649 /*< CalendarFactory.js */ 4650 /* 4651 * CalendarFactory.js - Constructs new instances of the right subclass of Calendar 4652 * 4653 * Copyright © 2015, JEDLSoft 4654 * 4655 * Licensed under the Apache License, Version 2.0 (the "License"); 4656 * you may not use this file except in compliance with the License. 4657 * You may obtain a copy of the License at 4658 * 4659 * http://www.apache.org/licenses/LICENSE-2.0 4660 * 4661 * Unless required by applicable law or agreed to in writing, software 4662 * distributed under the License is distributed on an "AS IS" BASIS, 4663 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4664 * 4665 * See the License for the specific language governing permissions and 4666 * limitations under the License. 4667 */ 4668 4669 /* !depends 4670 ilib.js 4671 Locale.js 4672 LocaleInfo.js 4673 Calendar.js 4674 */ 4675 4676 4677 /** 4678 * Factory method to create a new instance of a calendar subclass.<p> 4679 * 4680 * The options parameter can be an object that contains the following 4681 * properties: 4682 * 4683 * <ul> 4684 * <li><i>type</i> - specify the type of the calendar desired. The 4685 * list of valid values changes depending on which calendars are 4686 * defined. When assembling your iliball.js, include those calendars 4687 * you wish to use in your program or web page, and they will register 4688 * themselves with this factory method. The "official", "gregorian", 4689 * and "julian" calendars are all included by default, as they are the 4690 * standard calendars for much of the world. 4691 * <li><i>locale</i> - some calendars vary depending on the locale. 4692 * For example, the "official" calendar transitions from a Julian-style 4693 * calendar to a Gregorian-style calendar on a different date for 4694 * each country, as the governments of those countries decided to 4695 * adopt the Gregorian calendar at different times. 4696 * 4697 * <li><i>onLoad</i> - a callback function to call when the calendar object is fully 4698 * loaded. When the onLoad option is given, the calendar factory will attempt to 4699 * load any missing locale data using the ilib loader callback. 4700 * When the constructor is done (even if the data is already preassembled), the 4701 * onLoad function is called with the current instance as a parameter, so this 4702 * callback can be used with preassembled or dynamic loading or a mix of the two. 4703 * 4704 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 4705 * asynchronously. If this option is given as "false", then the "onLoad" 4706 * callback must be given, as the instance returned from this constructor will 4707 * not be usable for a while. 4708 * 4709 * <li><i>loadParams</i> - an object containing parameters to pass to the 4710 * loader callback function when locale data is missing. The parameters are not 4711 * interpretted or modified in any way. They are simply passed along. The object 4712 * may contain any property/value pairs as long as the calling code is in 4713 * agreement with the loader callback function as to what those parameters mean. 4714 * </ul> 4715 * 4716 * If a locale is specified, but no type, then the calendar that is default for 4717 * the locale will be instantiated and returned. If neither the type nor 4718 * the locale are specified, then the calendar for the default locale will 4719 * be used. 4720 * 4721 * @static 4722 * @param {Object=} options options controlling the construction of this instance, or 4723 * undefined to use the default options 4724 * @return {Calendar} an instance of a calendar object of the appropriate type 4725 */ 4726 var CalendarFactory = function (options) { 4727 var locale, 4728 type, 4729 sync = true, 4730 instance; 4731 4732 if (options) { 4733 if (options.locale) { 4734 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 4735 } 4736 4737 type = options.type || options.calendar; 4738 4739 if (typeof(options.sync) === 'boolean') { 4740 sync = options.sync; 4741 } 4742 } 4743 4744 if (!locale) { 4745 locale = new Locale(); // default locale 4746 } 4747 4748 if (!type) { 4749 new LocaleInfo(locale, { 4750 sync: sync, 4751 loadParams: options && options.loadParams, 4752 onLoad: function(info) { 4753 type = info.getCalendar(); 4754 4755 instance = CalendarFactory._init(type, options); 4756 } 4757 }); 4758 } else { 4759 instance = CalendarFactory._init(type, options); 4760 } 4761 4762 return instance; 4763 }; 4764 4765 /** 4766 * Map calendar names to classes to initialize in the dynamic code model. 4767 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 4768 * @private 4769 */ 4770 CalendarFactory._dynMap = { 4771 "coptic": "Coptic", 4772 "ethiopic": "Ethiopic", 4773 "gregorian": "Gregorian", 4774 "han": "Han", 4775 "hebrew": "Hebrew", 4776 "islamic": "Islamic", 4777 "julian": "Julian", 4778 "persian": "Persian", 4779 "persian-algo": "PersianAlgo", 4780 "thaisolar": "ThaiSolar" 4781 }; 4782 4783 /** 4784 * Dynamically load the code for a calendar and calendar class if necessary. 4785 * @protected 4786 */ 4787 CalendarFactory._dynLoadCalendar = function (name) { 4788 if (!Calendar._constructors[name]) { 4789 var entry = CalendarFactory._dynMap[name]; 4790 if (entry) { 4791 Calendar._constructors[name] = require("./" + entry + "Cal.js"); 4792 } 4793 } 4794 return Calendar._constructors[name]; 4795 }; 4796 4797 /** @private */ 4798 CalendarFactory._init = function(type, options) { 4799 var cons; 4800 4801 if (ilib.isDynCode()) { 4802 CalendarFactory._dynLoadCalendar(type); 4803 } 4804 4805 cons = Calendar._constructors[type]; 4806 4807 // pass the same options through to the constructor so the subclass 4808 // has the ability to do something with if it needs to 4809 if (!cons && typeof(options.onLoad) === "function") { 4810 options.onLoad(undefined); 4811 } 4812 return cons && new cons(options); 4813 }; 4814 4815 /** 4816 * Return an array of known calendar types that the factory method can instantiate. 4817 * 4818 * @return {Array.<string>} an array of calendar types 4819 */ 4820 CalendarFactory.getCalendars = function () { 4821 var arr = [], 4822 c; 4823 4824 if (ilib.isDynCode()) { 4825 for (c in CalendarFactory._dynMap) { 4826 CalendarFactory._dynLoadCalendar(c); 4827 } 4828 } 4829 4830 for (c in Calendar._constructors) { 4831 if (c && Calendar._constructors[c]) { 4832 arr.push(c); // code like a pirate 4833 } 4834 } 4835 4836 return arr; 4837 }; 4838 4839 4840 4841 /*< IDate.js */ 4842 /* 4843 * IDate.js - Represent a date in any calendar. This class is subclassed for each 4844 * calendar and includes some shared functionality. 4845 * 4846 * Copyright © 2012-2015, JEDLSoft 4847 * 4848 * Licensed under the Apache License, Version 2.0 (the "License"); 4849 * you may not use this file except in compliance with the License. 4850 * You may obtain a copy of the License at 4851 * 4852 * http://www.apache.org/licenses/LICENSE-2.0 4853 * 4854 * Unless required by applicable law or agreed to in writing, software 4855 * distributed under the License is distributed on an "AS IS" BASIS, 4856 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 4857 * 4858 * See the License for the specific language governing permissions and 4859 * limitations under the License. 4860 */ 4861 4862 /* !depends LocaleInfo.js */ 4863 4864 4865 /** 4866 * @class 4867 * Superclass for all the calendar date classes that contains shared 4868 * functionality. This class is never instantiated on its own. Instead, 4869 * you should use the {@link DateFactory} function to manufacture a new 4870 * instance of a subclass of IDate. This class is called IDate for "ilib 4871 * date" so that it does not conflict with the built-in Javascript Date 4872 * class. 4873 * 4874 * @private 4875 * @constructor 4876 * @param {Object=} options The date components to initialize this date with 4877 */ 4878 var IDate = function(options) { 4879 }; 4880 4881 /* place for the subclasses to put their constructors so that the factory method 4882 * can find them. Do this to add your date after it's defined: 4883 * IDate._constructors["mytype"] = IDate.MyTypeConstructor; 4884 */ 4885 IDate._constructors = {}; 4886 4887 IDate.prototype = { 4888 getType: function() { 4889 return "date"; 4890 }, 4891 4892 /** 4893 * Return the unix time equivalent to this date instance. Unix time is 4894 * the number of milliseconds since midnight on Jan 1, 1970 UTC (Gregorian). This 4895 * method only returns a valid number for dates between midnight, 4896 * Jan 1, 1970 UTC (Gregorian) and Jan 19, 2038 at 3:14:07am UTC (Gregorian) when 4897 * the unix time runs out. If this instance encodes a date outside of that range, 4898 * this method will return -1. For date types that are not Gregorian, the point 4899 * in time represented by this date object will only give a return value if it 4900 * is in the correct range in the Gregorian calendar as given previously. 4901 * 4902 * @return {number} a number giving the unix time, or -1 if the date is outside the 4903 * valid unix time range 4904 */ 4905 getTime: function() { 4906 return this.rd.getTime(); 4907 }, 4908 4909 /** 4910 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 4911 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 4912 * (or the type "time_t" in C/C++) is only encoded with an unsigned 32 bit integer, and thus 4913 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 4914 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 4915 * after Jan 1, 1970, and even more interestingly, 100 million days worth of time before 4916 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 4917 * range. If this instance encodes a date outside of that range, this method will return 4918 * NaN. 4919 * 4920 * @return {number} a number giving the extended unix time, or Nan if the date is outside 4921 * the valid extended unix time range 4922 */ 4923 getTimeExtended: function() { 4924 return this.rd.getTimeExtended(); 4925 }, 4926 4927 /** 4928 * Set the time of this instance according to the given unix time. Unix time is 4929 * the number of milliseconds since midnight on Jan 1, 1970. 4930 * 4931 * @param {number} millis the unix time to set this date to in milliseconds 4932 */ 4933 setTime: function(millis) { 4934 this.rd = this.newRd({ 4935 unixtime: millis, 4936 cal: this.cal 4937 }); 4938 this._calcDateComponents(); 4939 }, 4940 4941 getDays: function() { 4942 return this.day; 4943 }, 4944 getMonths: function() { 4945 return this.month; 4946 }, 4947 getYears: function() { 4948 return this.year; 4949 }, 4950 getHours: function() { 4951 return this.hour; 4952 }, 4953 getMinutes: function() { 4954 return this.minute; 4955 }, 4956 getSeconds: function() { 4957 return this.second; 4958 }, 4959 getMilliseconds: function() { 4960 return this.millisecond; 4961 }, 4962 getEra: function() { 4963 return (this.year < 1) ? -1 : 1; 4964 }, 4965 4966 setDays: function(day) { 4967 this.day = parseInt(day, 10) || 1; 4968 this.rd._setDateComponents(this); 4969 }, 4970 setMonths: function(month) { 4971 this.month = parseInt(month, 10) || 1; 4972 this.rd._setDateComponents(this); 4973 }, 4974 setYears: function(year) { 4975 this.year = parseInt(year, 10) || 0; 4976 this.rd._setDateComponents(this); 4977 }, 4978 4979 setHours: function(hour) { 4980 this.hour = parseInt(hour, 10) || 0; 4981 this.rd._setDateComponents(this); 4982 }, 4983 setMinutes: function(minute) { 4984 this.minute = parseInt(minute, 10) || 0; 4985 this.rd._setDateComponents(this); 4986 }, 4987 setSeconds: function(second) { 4988 this.second = parseInt(second, 10) || 0; 4989 this.rd._setDateComponents(this); 4990 }, 4991 setMilliseconds: function(milli) { 4992 this.millisecond = parseInt(milli, 10) || 0; 4993 this.rd._setDateComponents(this); 4994 }, 4995 4996 /** 4997 * Return a new date instance in the current calendar that represents the first instance 4998 * of the given day of the week before the current date. The day of the week is encoded 4999 * as a number where 0 = Sunday, 1 = Monday, etc. 5000 * 5001 * @param {number} dow the day of the week before the current date that is being sought 5002 * @return {IDate} the date being sought 5003 */ 5004 before: function (dow) { 5005 return new this.constructor({ 5006 rd: this.rd.before(dow, this.offset), 5007 timezone: this.timezone 5008 }); 5009 }, 5010 5011 /** 5012 * Return a new date instance in the current calendar that represents the first instance 5013 * of the given day of the week after the current date. The day of the week is encoded 5014 * as a number where 0 = Sunday, 1 = Monday, etc. 5015 * 5016 * @param {number} dow the day of the week after the current date that is being sought 5017 * @return {IDate} the date being sought 5018 */ 5019 after: function (dow) { 5020 return new this.constructor({ 5021 rd: this.rd.after(dow, this.offset), 5022 timezone: this.timezone 5023 }); 5024 }, 5025 5026 /** 5027 * Return a new Gregorian date instance that represents the first instance of the 5028 * given day of the week on or before the current date. The day of the week is encoded 5029 * as a number where 0 = Sunday, 1 = Monday, etc. 5030 * 5031 * @param {number} dow the day of the week on or before the current date that is being sought 5032 * @return {IDate} the date being sought 5033 */ 5034 onOrBefore: function (dow) { 5035 return new this.constructor({ 5036 rd: this.rd.onOrBefore(dow, this.offset), 5037 timezone: this.timezone 5038 }); 5039 }, 5040 5041 /** 5042 * Return a new Gregorian date instance that represents the first instance of the 5043 * given day of the week on or after the current date. The day of the week is encoded 5044 * as a number where 0 = Sunday, 1 = Monday, etc. 5045 * 5046 * @param {number} dow the day of the week on or after the current date that is being sought 5047 * @return {IDate} the date being sought 5048 */ 5049 onOrAfter: function (dow) { 5050 return new this.constructor({ 5051 rd: this.rd.onOrAfter(dow, this.offset), 5052 timezone: this.timezone 5053 }); 5054 }, 5055 5056 /** 5057 * Return a Javascript Date object that is equivalent to this date 5058 * object. 5059 * 5060 * @return {Date|undefined} a javascript Date object 5061 */ 5062 getJSDate: function() { 5063 var unix = this.rd.getTimeExtended(); 5064 return isNaN(unix) ? undefined : new Date(unix); 5065 }, 5066 5067 /** 5068 * Return the Rata Die (fixed day) number of this date. 5069 * 5070 * @protected 5071 * @return {number} the rd date as a number 5072 */ 5073 getRataDie: function() { 5074 return this.rd.getRataDie(); 5075 }, 5076 5077 /** 5078 * Set the date components of this instance based on the given rd. 5079 * @protected 5080 * @param {number} rd the rata die date to set 5081 */ 5082 setRd: function (rd) { 5083 this.rd = this.newRd({ 5084 rd: rd, 5085 cal: this.cal 5086 }); 5087 this._calcDateComponents(); 5088 }, 5089 5090 /** 5091 * Return the Julian Day equivalent to this calendar date as a number. 5092 * 5093 * @return {number} the julian date equivalent of this date 5094 */ 5095 getJulianDay: function() { 5096 return this.rd.getJulianDay(); 5097 }, 5098 5099 /** 5100 * Set the date of this instance using a Julian Day. 5101 * @param {number|JulianDay} date the Julian Day to use to set this date 5102 */ 5103 setJulianDay: function (date) { 5104 this.rd = this.newRd({ 5105 julianday: (typeof(date) === 'object') ? date.getDate() : date, 5106 cal: this.cal 5107 }); 5108 this._calcDateComponents(); 5109 }, 5110 5111 /** 5112 * Return the time zone associated with this date, or 5113 * undefined if none was specified in the constructor. 5114 * 5115 * @return {string|undefined} the name of the time zone for this date instance 5116 */ 5117 getTimeZone: function() { 5118 return this.timezone || "local"; 5119 }, 5120 5121 /** 5122 * Set the time zone associated with this date. 5123 * @param {string=} tzName the name of the time zone to set into this date instance, 5124 * or "undefined" to unset the time zone 5125 */ 5126 setTimeZone: function (tzName) { 5127 if (!tzName || tzName === "") { 5128 // same as undefining it 5129 this.timezone = undefined; 5130 this.tz = undefined; 5131 } else if (typeof(tzName) === 'string') { 5132 this.timezone = tzName; 5133 this.tz = undefined; 5134 // assuming the same UTC time, but a new time zone, now we have to 5135 // recalculate what the date components are 5136 this._calcDateComponents(); 5137 } 5138 }, 5139 5140 /** 5141 * Return the rd number of the first Sunday of the given ISO year. 5142 * @protected 5143 * @param {number} year the year for which the first Sunday is being sought 5144 * @return {number} the rd of the first Sunday of the ISO year 5145 */ 5146 firstSunday: function (year) { 5147 var firstDay = this.newRd({ 5148 year: year, 5149 month: 1, 5150 day: 1, 5151 hour: 0, 5152 minute: 0, 5153 second: 0, 5154 millisecond: 0, 5155 cal: this.cal 5156 }); 5157 var firstThu = this.newRd({ 5158 rd: firstDay.onOrAfter(4), 5159 cal: this.cal 5160 }); 5161 return firstThu.before(0); 5162 }, 5163 5164 /** 5165 * Return the ISO 8601 week number in the current year for the current date. The week 5166 * number ranges from 0 to 55, as some years have 55 weeks assigned to them in some 5167 * calendars. 5168 * 5169 * @return {number} the week number for the current date 5170 */ 5171 getWeekOfYear: function() { 5172 var rd = Math.floor(this.rd.getRataDie()); 5173 var year = this._calcYear(rd + this.offset); 5174 var yearStart = this.firstSunday(year); 5175 var nextYear; 5176 5177 // if we have a January date, it may be in this ISO year or the previous year 5178 if (rd < yearStart) { 5179 yearStart = this.firstSunday(year-1); 5180 } else { 5181 // if we have a late December date, it may be in this ISO year, or the next year 5182 nextYear = this.firstSunday(year+1); 5183 if (rd >= nextYear) { 5184 yearStart = nextYear; 5185 } 5186 } 5187 5188 return Math.floor((rd-yearStart)/7) + 1; 5189 }, 5190 5191 /** 5192 * Return the ordinal number of the week within the month. The first week of a month is 5193 * the first one that contains 4 or more days in that month. If any days precede this 5194 * first week, they are marked as being in week 0. This function returns values from 0 5195 * through 6.<p> 5196 * 5197 * The locale is a required parameter because different locales that use the same 5198 * Gregorian calendar consider different days of the week to be the beginning of 5199 * the week. This can affect the week of the month in which some days are located. 5200 * 5201 * @param {Locale|string} locale the locale or locale spec to use when figuring out 5202 * the first day of the week 5203 * @return {number} the ordinal number of the week within the current month 5204 */ 5205 getWeekOfMonth: function(locale) { 5206 var li = new LocaleInfo(locale); 5207 5208 var first = this.newRd({ 5209 year: this._calcYear(this.rd.getRataDie()+this.offset), 5210 month: this.getMonths(), 5211 day: 1, 5212 hour: 0, 5213 minute: 0, 5214 second: 0, 5215 millisecond: 0, 5216 cal: this.cal 5217 }); 5218 var weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 5219 5220 if (weekStart - first.getRataDie() > 3) { 5221 // if the first week has 4 or more days in it of the current month, then consider 5222 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 5223 // one week earlier. 5224 weekStart -= 7; 5225 } 5226 return Math.floor((this.rd.getRataDie() - weekStart) / 7) + 1; 5227 } 5228 }; 5229 5230 5231 /*< JulianDay.js */ 5232 /* 5233 * JulianDay.js - A Julian Day object. 5234 * 5235 * Copyright © 2012-2015, JEDLSoft 5236 * 5237 * Licensed under the Apache License, Version 2.0 (the "License"); 5238 * you may not use this file except in compliance with the License. 5239 * You may obtain a copy of the License at 5240 * 5241 * http://www.apache.org/licenses/LICENSE-2.0 5242 * 5243 * Unless required by applicable law or agreed to in writing, software 5244 * distributed under the License is distributed on an "AS IS" BASIS, 5245 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5246 * 5247 * See the License for the specific language governing permissions and 5248 * limitations under the License. 5249 */ 5250 5251 /** 5252 * @class 5253 * A Julian Day class. A Julian Day is a date based on the Julian Day count 5254 * of time invented by Joseph Scaliger in 1583 for use with astronomical calculations. 5255 * Do not confuse it with a date in the Julian calendar, which it has very 5256 * little in common with. The naming is unfortunately close, and comes from history.<p> 5257 * 5258 * 5259 * @constructor 5260 * @param {number} num the Julian Day expressed as a floating point number 5261 */ 5262 var JulianDay = function(num) { 5263 this.jd = num; 5264 this.days = Math.floor(this.jd); 5265 this.frac = num - this.days; 5266 }; 5267 5268 JulianDay.prototype = { 5269 /** 5270 * Return the integral portion of this Julian Day instance. This corresponds to 5271 * the number of days since the beginning of the epoch. 5272 * 5273 * @return {number} the integral portion of this Julian Day 5274 */ 5275 getDays: function() { 5276 return this.days; 5277 }, 5278 5279 /** 5280 * Set the date of this Julian Day instance. 5281 * 5282 * @param {number} days the julian date expressed as a floating point number 5283 */ 5284 setDays: function(days) { 5285 this.days = Math.floor(days); 5286 this.jd = this.days + this.frac; 5287 }, 5288 5289 /** 5290 * Return the fractional portion of this Julian Day instance. This portion 5291 * corresponds to the time of day for the instance. 5292 */ 5293 getDayFraction: function() { 5294 return this.frac; 5295 }, 5296 5297 /** 5298 * Set the fractional part of the Julian Day. The fractional part represents 5299 * the portion of a fully day. Julian dates start at noon, and proceed until 5300 * noon of the next day. That would mean midnight is represented as a fractional 5301 * part of 0.5. 5302 * 5303 * @param {number} fraction The fractional part of the Julian date 5304 */ 5305 setDayFraction: function(fraction) { 5306 var t = Math.floor(fraction); 5307 this.frac = fraction - t; 5308 this.jd = this.days + this.frac; 5309 }, 5310 5311 /** 5312 * Return the Julian Day expressed as a floating point number. 5313 * @return {number} the Julian Day as a number 5314 */ 5315 getDate: function () { 5316 return this.jd; 5317 }, 5318 5319 /** 5320 * Set the date of this Julian Day instance. 5321 * 5322 * @param {number} num the numeric Julian Day to set into this instance 5323 */ 5324 setDate: function (num) { 5325 this.jd = num; 5326 }, 5327 5328 /** 5329 * Add an offset to the current date instance. The offset should be expressed in 5330 * terms of Julian days. That is, each integral unit represents one day of time, and 5331 * fractional part represents a fraction of a regular 24-hour day. 5332 * 5333 * @param {number} offset an amount to add (or subtract) to the current result instance. 5334 */ 5335 addDate: function(offset) { 5336 if (typeof(offset) === 'number') { 5337 this.jd += offset; 5338 this.days = Math.floor(this.jd); 5339 this.frac = this.jd - this.days; 5340 } 5341 } 5342 }; 5343 5344 5345 5346 /*< DateFactory.js */ 5347 /* 5348 * DateFactory.js - Factory class to create the right subclasses of a date for any 5349 * calendar or locale. 5350 * 5351 * Copyright © 2012-2015, JEDLSoft 5352 * 5353 * Licensed under the Apache License, Version 2.0 (the "License"); 5354 * you may not use this file except in compliance with the License. 5355 * You may obtain a copy of the License at 5356 * 5357 * http://www.apache.org/licenses/LICENSE-2.0 5358 * 5359 * Unless required by applicable law or agreed to in writing, software 5360 * distributed under the License is distributed on an "AS IS" BASIS, 5361 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5362 * 5363 * See the License for the specific language governing permissions and 5364 * limitations under the License. 5365 */ 5366 5367 /* !depends ilib.js Locale.js LocaleInfo.js JulianDay.js JSUtils.js CalendarFactory.js IDate.js */ 5368 5369 5370 5371 // Statically depend on these even though we don't use them 5372 // to guarantee they are loaded into the cache already. 5373 5374 /** 5375 * Factory method to create a new instance of a date subclass.<p> 5376 * 5377 * The options parameter can be an object that contains the following 5378 * properties: 5379 * 5380 * <ul> 5381 * <li><i>type</i> - specify the type/calendar of the date desired. The 5382 * list of valid values changes depending on which calendars are 5383 * defined. When assembling your iliball.js, include those date type 5384 * you wish to use in your program or web page, and they will register 5385 * themselves with this factory method. The "gregorian", 5386 * and "julian" calendars are all included by default, as they are the 5387 * standard calendars for much of the world. If not specified, the type 5388 * of the date returned is the one that is appropriate for the locale. 5389 * This property may also be given as "calendar" instead of "type". 5390 * 5391 * <li><i>onLoad</i> - a callback function to call when the date object is fully 5392 * loaded. When the onLoad option is given, the date factory will attempt to 5393 * load any missing locale data using the ilib loader callback. 5394 * When the constructor is done (even if the data is already preassembled), the 5395 * onLoad function is called with the current instance as a parameter, so this 5396 * callback can be used with preassembled or dynamic loading or a mix of the two. 5397 * 5398 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 5399 * asynchronously. If this option is given as "false", then the "onLoad" 5400 * callback must be given, as the instance returned from this constructor will 5401 * not be usable for a while. 5402 * 5403 * <li><i>loadParams</i> - an object containing parameters to pass to the 5404 * loader callback function when locale data is missing. The parameters are not 5405 * interpretted or modified in any way. They are simply passed along. The object 5406 * may contain any property/value pairs as long as the calling code is in 5407 * agreement with the loader callback function as to what those parameters mean. 5408 * </ul> 5409 * 5410 * The options object is also passed down to the date constructor, and 5411 * thus can contain the the properties as the date object being instantiated. 5412 * See the documentation for {@link GregorianDate}, and other 5413 * subclasses for more details on other parameter that may be passed in.<p> 5414 * 5415 * Please note that if you do not give the type parameter, this factory 5416 * method will create a date object that is appropriate for the calendar 5417 * that is most commonly used in the specified or current ilib locale. 5418 * For example, in Thailand, the most common calendar is the Thai solar 5419 * calendar. If the current locale is "th-TH" (Thai for Thailand) and you 5420 * use this factory method to construct a new date without specifying the 5421 * type, it will automatically give you back an instance of 5422 * {@link ThaiSolarDate}. This is convenient because you do not 5423 * need to know which locales use which types of dates. In fact, you 5424 * should always use this factory method to make new date instances unless 5425 * you know that you specifically need a date in a particular calendar.<p> 5426 * 5427 * Also note that when you pass in the date components such as year, month, 5428 * day, etc., these components should be appropriate for the given date 5429 * being instantiated. That is, in our Thai example in the previous 5430 * paragraph, the year and such should be given as a Thai solar year, not 5431 * the Gregorian year that you get from the Javascript Date class. In 5432 * order to initialize a date instance when you don't know what subclass 5433 * will be instantiated for the locale, use a parameter such as "unixtime" 5434 * or "julianday" which are unambiguous and based on UTC time, instead of 5435 * the year/month/date date components. The date components for that UTC 5436 * time will be calculated and the time zone offset will be automatically 5437 * factored in. 5438 * 5439 * @static 5440 * @param {Object=} options options controlling the construction of this instance, or 5441 * undefined to use the default options 5442 * @return {IDate} an instance of a calendar object of the appropriate type 5443 */ 5444 var DateFactory = function(options) { 5445 var locale, 5446 type, 5447 sync = true, 5448 obj; 5449 5450 if (options) { 5451 if (options.locale) { 5452 locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 5453 } 5454 5455 type = options.type || options.calendar; 5456 5457 if (typeof(options.sync) === 'boolean') { 5458 sync = options.sync; 5459 } 5460 } 5461 5462 if (!locale) { 5463 locale = new Locale(); // default locale 5464 } 5465 5466 if (!type) { 5467 new LocaleInfo(locale, { 5468 sync: sync, 5469 loadParams: options && options.loadParams, 5470 onLoad: function(info) { 5471 type = info.getCalendar(); 5472 5473 obj = DateFactory._init(type, options); 5474 } 5475 }); 5476 } else { 5477 obj = DateFactory._init(type, options); 5478 } 5479 5480 return obj 5481 }; 5482 5483 /** 5484 * Map calendar names to classes to initialize in the dynamic code model. 5485 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 5486 * @private 5487 */ 5488 DateFactory._dynMap = { 5489 "coptic": "Coptic", 5490 "ethiopic": "Ethiopic", 5491 "gregorian": "Gregorian", 5492 "han": "Han", 5493 "hebrew": "Hebrew", 5494 "islamic": "Islamic", 5495 "julian": "Julian", 5496 "persian": "Persian", 5497 "persian-algo": "PersianAlgo", 5498 "thaisolar": "ThaiSolar" 5499 }; 5500 5501 /** 5502 * Dynamically load the code for a calendar and calendar class if necessary. 5503 * @protected 5504 */ 5505 DateFactory._dynLoadDate = function (name) { 5506 if (!IDate._constructors[name]) { 5507 var entry = DateFactory._dynMap[name]; 5508 if (entry) { 5509 IDate._constructors[name] = require("./" + entry + "Date.js"); 5510 } 5511 } 5512 return IDate._constructors[name]; 5513 }; 5514 5515 /** 5516 * @protected 5517 * @static 5518 */ 5519 DateFactory._init = function(type, options) { 5520 var cons; 5521 5522 if (ilib.isDynCode()) { 5523 DateFactory._dynLoadDate(type); 5524 CalendarFactory._dynLoadCalendar(type); 5525 } 5526 5527 cons = IDate._constructors[type]; 5528 5529 // pass the same options through to the constructor so the subclass 5530 // has the ability to do something with if it needs to 5531 if (!cons && typeof(options.onLoad) === "function") { 5532 options.onLoad(undefined); 5533 } 5534 return cons && new cons(options); 5535 }; 5536 5537 /** 5538 * Convert JavaScript Date objects and other types into native Dates. This accepts any 5539 * string or number that can be translated by the JavaScript Date class, 5540 * (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/parse) 5541 * any JavaScript Date classed object, any IDate subclass, an JulianDay object, an object 5542 * containing the normal options to initialize an IDate instance, or null (will 5543 * return null or undefined if input is null or undefined). Normal output is 5544 * a standard native subclass of the IDate object as appropriate for the locale. 5545 * 5546 * @static 5547 * @protected 5548 * @param {IDate|Object|JulianDay|Date|string|number=} inDate The input date object, string or Number. 5549 * @param {IString|string=} timezone timezone to use if a new date object is created 5550 * @param {Locale|string=} locale locale to use when constructing an IDate 5551 * @return {IDate|null|undefined} an IDate subclass equivalent to the given inDate 5552 */ 5553 DateFactory._dateToIlib = function(inDate, timezone, locale) { 5554 if (typeof(inDate) === 'undefined' || inDate === null) { 5555 return inDate; 5556 } 5557 if (inDate instanceof IDate) { 5558 return inDate; 5559 } 5560 if (typeof(inDate) === 'number') { 5561 return DateFactory({ 5562 unixtime: inDate, 5563 timezone: timezone, 5564 locale: locale 5565 }); 5566 } 5567 if (typeof(inDate) === 'string') { 5568 inDate = new Date(inDate); 5569 } 5570 if (JSUtils.isDate(inDate)) { 5571 return DateFactory({ 5572 unixtime: inDate.getTime(), 5573 timezone: timezone, 5574 locale: locale 5575 }); 5576 } 5577 if (inDate instanceof JulianDay) { 5578 return DateFactory({ 5579 jd: inDate, 5580 timezone: timezone, 5581 locale: locale 5582 }); 5583 } 5584 if (typeof(inDate) === 'object') { 5585 return DateFactory(inDate); 5586 } 5587 return DateFactory({ 5588 unixtime: inDate.getTime(), 5589 timezone: timezone, 5590 locale: locale 5591 }); 5592 }; 5593 5594 5595 5596 /*< ResBundle.js */ 5597 /* 5598 * ResBundle.js - Resource bundle definition 5599 * 5600 * Copyright © 2012-2016, JEDLSoft 5601 * 5602 * Licensed under the Apache License, Version 2.0 (the "License"); 5603 * you may not use this file except in compliance with the License. 5604 * You may obtain a copy of the License at 5605 * 5606 * http://www.apache.org/licenses/LICENSE-2.0 5607 * 5608 * Unless required by applicable law or agreed to in writing, software 5609 * distributed under the License is distributed on an "AS IS" BASIS, 5610 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 5611 * 5612 * See the License for the specific language governing permissions and 5613 * limitations under the License. 5614 */ 5615 5616 // !depends ilib.js Locale.js LocaleInfo.js IString.js Utils.js JSUtils.js 5617 5618 // !data pseudomap 5619 5620 5621 5622 5623 /** 5624 * @class 5625 * Create a new resource bundle instance. The resource bundle loads strings 5626 * appropriate for a particular locale and provides them via the getString 5627 * method.<p> 5628 * 5629 * The options object may contain any (or none) of the following properties: 5630 * 5631 * <ul> 5632 * <li><i>locale</i> - The locale of the strings to load. If not specified, the default 5633 * locale is the the default for the web page or app in which the bundle is 5634 * being loaded. 5635 * 5636 * <li><i>name</i> - Base name of the resource bundle to load. If not specified the default 5637 * base name is "resources". 5638 * 5639 * <li><i>type</i> - Name the type of strings this bundle contains. Valid values are 5640 * "xml", "html", "text", "c", or "raw". The default is "text". If the type is "xml" or "html", 5641 * then XML/HTML entities and tags are not pseudo-translated. During a real translation, 5642 * HTML character entities are translated to their corresponding characters in a source 5643 * string before looking that string up in the translations. Also, the characters "<", ">", 5644 * and "&" are converted to entities again in the output, but characters are left as they 5645 * are. If the type is "xml", "html", or "text" types, then the replacement parameter names 5646 * are not pseudo-translated as well so that the output can be used for formatting with 5647 * the IString class. If the type is "c" then all C language style printf replacement 5648 * parameters (eg. "%s" and "%d") are skipped automatically. If the type is raw, all characters 5649 * are pseudo-translated, including replacement parameters as well as XML/HTML tags and entities. 5650 * 5651 * <li><i>lengthen</i> - when pseudo-translating the string, tell whether or not to 5652 * automatically lengthen the string to simulate "long" languages such as German 5653 * or French. This is a boolean value. Default is false. 5654 * 5655 * <li><i>missing</i> - what to do when a resource is missing. The choices are: 5656 * <ul> 5657 * <li><i>source</i> - return the source string unchanged 5658 * <li><i>pseudo</i> - return the pseudo-translated source string, translated to the 5659 * script of the locale if the mapping is available, or just the default Latin 5660 * pseudo-translation if not 5661 * <li><i>empty</i> - return the empty string 5662 * </ul> 5663 * The default behaviour is the same as before, which is to return the source string 5664 * unchanged. 5665 * 5666 * <li><i>onLoad</i> - a callback function to call when the resources are fully 5667 * loaded. When the onLoad option is given, this class will attempt to 5668 * load any missing locale data using the ilib loader callback. 5669 * When the constructor is done (even if the data is already preassembled), the 5670 * onLoad function is called with the current instance as a parameter, so this 5671 * callback can be used with preassembled or dynamic loading or a mix of the two. 5672 * 5673 * <li>sync - tell whether to load any missing locale data synchronously or 5674 * asynchronously. If this option is given as "false", then the "onLoad" 5675 * callback must be given, as the instance returned from this constructor will 5676 * not be usable for a while. 5677 * 5678 * <li><i>loadParams</i> - an object containing parameters to pass to the 5679 * loader callback function when locale data is missing. The parameters are not 5680 * interpretted or modified in any way. They are simply passed along. The object 5681 * may contain any property/value pairs as long as the calling code is in 5682 * agreement with the loader callback function as to what those parameters mean. 5683 * </ul> 5684 * 5685 * The locale option may be given as a locale spec string or as an 5686 * Locale object. If the locale option is not specified, then strings for 5687 * the default locale will be loaded.<p> 5688 * 5689 * The name option can be used to put groups of strings together in a 5690 * single bundle. The strings will then appear together in a JS object in 5691 * a JS file that can be included before the ilib.<p> 5692 * 5693 * A resource bundle with a particular name is actually a set of bundles 5694 * that are each specific to a language, a language plus a region, etc. 5695 * All bundles with the same base name should 5696 * contain the same set of source strings, but with different translations for 5697 * the given locale. The user of the bundle does not need to be aware of 5698 * the locale of the bundle, as long as it contains values for the strings 5699 * it needs.<p> 5700 * 5701 * Strings in bundles for a particular locale are inherited from parent bundles 5702 * that are more generic. In general, the hierarchy is as follows (from 5703 * least locale-specific to most locale-specific): 5704 * 5705 * <ol> 5706 * <li> language 5707 * <li> region 5708 * <li> language_script 5709 * <li> language_region 5710 * <li> region_variant 5711 * <li> language_script_region 5712 * <li> language_region_variant 5713 * <li> language_script_region_variant 5714 * </ol> 5715 * 5716 * That is, if the translation for a string does not exist in the current 5717 * locale, the more-generic parent locale is searched for the string. In the 5718 * worst case scenario, the string is not found in the base locale's strings. 5719 * In this case, the missing option guides this class on what to do. If 5720 * the missing option is "source", then the original source is returned as 5721 * the translation. If it is "empty", the empty string is returned. If it 5722 * is "pseudo", then the pseudo-translated string that is appropriate for 5723 * the default script of the locale is returned.<p> 5724 * 5725 * This allows developers to create code with new or changed strings in it and check in that 5726 * code without waiting for the translations to be done first. The translated 5727 * version of the app or web site will still function properly, but will show 5728 * a spurious untranslated string here and there until the translations are 5729 * done and also checked in.<p> 5730 * 5731 * The base is whatever language your developers use to code in. For 5732 * a German web site, strings in the source code may be written in German 5733 * for example. Often this base is English, as many web sites are coded in 5734 * English, but that is not required.<p> 5735 * 5736 * The strings can be extracted with the ilib localization tool (which will be 5737 * shipped at some future time.) Once the strings 5738 * have been translated, the set of translated files can be generated with the 5739 * same tool. The output from the tool can be used as input to the ResBundle 5740 * object. It is up to the web page or app to make sure the JS file that defines 5741 * the bundle is included before creating the ResBundle instance.<p> 5742 * 5743 * A special locale "zxx-XX" is used as the pseudo-translation locale because 5744 * zxx means "no linguistic information" in the ISO 639 standard, and the region 5745 * code XX is defined to be user-defined in the ISO 3166 standard. 5746 * Pseudo-translation is a locale where the translations are generated on 5747 * the fly based on the contents of the source string. Characters in the source 5748 * string are replaced with other characters and returned. 5749 * 5750 * Example. If the source string is: 5751 * 5752 * <pre> 5753 * "This is a string" 5754 * </pre> 5755 * 5756 * then the pseudo-translated version might look something like this: 5757 * 5758 * <pre> 5759 * "Ţħïş ïş á şţřïñĝ" 5760 * </pre> 5761 * <p> 5762 * 5763 * Pseudo-translation can be used to test that your app or web site is translatable 5764 * before an actual translation has happened. These bugs can then be fixed 5765 * before the translation starts, avoiding an explosion of bugs later when 5766 * each language's tester registers the same bug complaining that the same 5767 * string is not translated. When pseudo-localizing with 5768 * the Latin script, this allows the strings to be readable in the UI in the 5769 * source language (if somewhat funky-looking), 5770 * so that a tester can easily verify that the string is properly externalized 5771 * and loaded from a resource bundle without the need to be able to read a 5772 * foreign language.<p> 5773 * 5774 * If one of a list of script tags is given in the pseudo-locale specifier, then the 5775 * pseudo-localization can map characters to very rough transliterations of 5776 * characters in the given script. For example, zxx-Hebr-XX maps strings to 5777 * Hebrew characters, which can be used to test your UI in a right-to-left 5778 * language to catch bidi bugs before a translation is done. Currently, the 5779 * list of target scripts includes Hebrew (Hebr), Chinese Simplified Han (Hans), 5780 * and Cyrillic (Cyrl) with more to be added later. If no script is explicitly 5781 * specified in the locale spec, or if the script is not supported, 5782 * then the default mapping maps Latin base characters to accented versions of 5783 * those Latin characters as in the example above. 5784 * 5785 * When the "lengthen" property is set to true in the options, the 5786 * pseudotranslation code will add digits to the end of the string to simulate 5787 * the lengthening that occurs when translating to other languages. The above 5788 * example will come out like this: 5789 * 5790 * <pre> 5791 * "Ţħïş ïş á şţřïñĝ76543210" 5792 * </pre> 5793 * 5794 * The string is lengthened according to the length of the source string. If 5795 * the source string is less than 20 characters long, the string is lengthened 5796 * by 50%. If the source string is 20-40 5797 * characters long, the string is lengthened by 33%. If te string is greater 5798 * than 40 characters long, the string is lengthened by 20%.<p> 5799 * 5800 * The pseudotranslation always ends a string with the digit "0". If you do 5801 * not see the digit "0" in the UI for your app, you know that truncation 5802 * has occurred, and the number you see at the end of the string tells you 5803 * how many characters were truncated.<p> 5804 * 5805 * 5806 * @constructor 5807 * @param {?Object} options Options controlling how the bundle is created 5808 */ 5809 var ResBundle = function (options) { 5810 var lookupLocale, spec; 5811 5812 this.locale = new Locale(); // use the default locale 5813 this.baseName = "strings"; 5814 this.type = "text"; 5815 this.loadParams = {}; 5816 this.missing = "source"; 5817 this.sync = true; 5818 5819 if (options) { 5820 if (options.locale) { 5821 this.locale = (typeof(options.locale) === 'string') ? 5822 new Locale(options.locale) : 5823 options.locale; 5824 } 5825 if (options.name) { 5826 this.baseName = options.name; 5827 } 5828 if (options.type) { 5829 this.type = options.type; 5830 } 5831 this.lengthen = options.lengthen || false; 5832 5833 if (typeof(options.sync) !== 'undefined') { 5834 this.sync = !!options.sync; 5835 } 5836 5837 if (typeof(options.loadParams) !== 'undefined') { 5838 this.loadParams = options.loadParams; 5839 } 5840 if (typeof(options.missing) !== 'undefined') { 5841 if (options.missing === "pseudo" || options.missing === "empty") { 5842 this.missing = options.missing; 5843 } 5844 } 5845 } else { 5846 options = {sync: true}; 5847 } 5848 5849 this.map = {}; 5850 5851 if (!ilib.data.cache.ResBundle) { 5852 ilib.data.cache.ResBundle = {}; 5853 } 5854 5855 lookupLocale = this.locale.isPseudo() ? new Locale("en-US") : this.locale; 5856 var object = "ResBundle-" + this.baseName; 5857 5858 Utils.loadData({ 5859 object: object, 5860 locale: lookupLocale, 5861 name: this.baseName + ".json", 5862 sync: this.sync, 5863 loadParams: this.loadParams, 5864 callback: ilib.bind(this, function (map) { 5865 if (!map) { 5866 map = ilib.data[this.baseName] || {}; 5867 spec = lookupLocale.getSpec().replace(/-/g, '_'); 5868 ilib.data.cache[object][spec] = map; 5869 } 5870 this.map = map; 5871 if (this.locale.isPseudo()) { 5872 if (!ilib.data.cache.ResBundle.pseudomap) { 5873 ilib.data.cache.ResBundle.pseudomap = {}; 5874 } 5875 5876 this._loadPseudo(this.locale, options.onLoad); 5877 } else if (this.missing === "pseudo") { 5878 if (!ilib.data.cache.ResBundle.pseudomap) { 5879 ilib.data.cache.ResBundle.pseudomap = {}; 5880 } 5881 5882 new LocaleInfo(this.locale, { 5883 sync: this.sync, 5884 loadParams: this.loadParams, 5885 onLoad: ilib.bind(this, function (li) { 5886 var pseudoLocale = new Locale("zxx", "XX", undefined, li.getDefaultScript()); 5887 this._loadPseudo(pseudoLocale, options.onLoad); 5888 }) 5889 }); 5890 } else { 5891 if (typeof(options.onLoad) === 'function') { 5892 options.onLoad(this); 5893 } 5894 } 5895 }) 5896 }); 5897 5898 // console.log("Merged resources " + this.locale.toString() + " are: " + JSON.stringify(this.map)); 5899 //if (!this.locale.isPseudo() && JSUtils.isEmpty(this.map)) { 5900 // console.log("Resources for bundle " + this.baseName + " locale " + this.locale.toString() + " are not available."); 5901 //} 5902 }; 5903 5904 ResBundle.defaultPseudo = ilib.data.pseudomap || { 5905 "a": "à", 5906 "e": "ë", 5907 "i": "í", 5908 "o": "õ", 5909 "u": "ü", 5910 "y": "ÿ", 5911 "A": "Ã", 5912 "E": "Ë", 5913 "I": "Ï", 5914 "O": "Ø", 5915 "U": "Ú", 5916 "Y": "Ŷ" 5917 }; 5918 5919 ResBundle.prototype = { 5920 /** 5921 * @protected 5922 */ 5923 _loadPseudo: function (pseudoLocale, onLoad) { 5924 Utils.loadData({ 5925 object: "ResBundle", 5926 locale: pseudoLocale, 5927 name: "pseudomap.json", 5928 sync: this.sync, 5929 loadParams: this.loadParams, 5930 callback: ilib.bind(this, function (map) { 5931 if (!map || JSUtils.isEmpty(map)) { 5932 map = ResBundle.defaultPseudo; 5933 var spec = pseudoLocale.getSpec().replace(/-/g, '_'); 5934 ilib.data.cache.ResBundle.pseudomap[spec] = map; 5935 } 5936 this.pseudomap = map; 5937 if (typeof(onLoad) === 'function') { 5938 onLoad(this); 5939 } 5940 }) 5941 }); 5942 }, 5943 5944 /** 5945 * Return the locale of this resource bundle. 5946 * @return {Locale} the locale of this resource bundle object 5947 */ 5948 getLocale: function () { 5949 return this.locale; 5950 }, 5951 5952 /** 5953 * Return the name of this resource bundle. This corresponds to the name option 5954 * given to the constructor. 5955 * @return {string} name of the the current instance 5956 */ 5957 getName: function () { 5958 return this.baseName; 5959 }, 5960 5961 /** 5962 * Return the type of this resource bundle. This corresponds to the type option 5963 * given to the constructor. 5964 * @return {string} type of the the current instance 5965 */ 5966 getType: function () { 5967 return this.type; 5968 }, 5969 5970 percentRE: new RegExp("%(\\d+\\$)?([\\-#\\+ 0,\\(])?(\\d+)?(\\.\\d+)?[bBhHsScCdoxXeEfgGaAtT%n]"), 5971 5972 /** 5973 * @private 5974 * Pseudo-translate a string 5975 */ 5976 _pseudo: function (str) { 5977 if (!str) { 5978 return undefined; 5979 } 5980 var ret = "", i; 5981 for (i = 0; i < str.length; i++) { 5982 if (this.type !== "raw") { 5983 if (this.type === "html" || this.type === "xml") { 5984 if (str.charAt(i) === '<') { 5985 ret += str.charAt(i++); 5986 while (i < str.length && str.charAt(i) !== '>') { 5987 ret += str.charAt(i++); 5988 } 5989 } else if (str.charAt(i) === '&') { 5990 ret += str.charAt(i++); 5991 while (i < str.length && str.charAt(i) !== ';' && str.charAt(i) !== ' ') { 5992 ret += str.charAt(i++); 5993 } 5994 } else if (str.charAt(i) === '\\' && str.charAt(i+1) === "u") { 5995 ret += str.substring(i, i+6); 5996 i += 6; 5997 } 5998 } else if (this.type === "c") { 5999 if (str.charAt(i) === "%") { 6000 var m = this.percentRE.exec(str.substring(i)); 6001 if (m && m.length) { 6002 // console.log("Match found: " + JSON.stringify(m[0].replace("%", "%%"))); 6003 ret += m[0]; 6004 i += m[0].length; 6005 } 6006 } 6007 6008 } 6009 if (i < str.length) { 6010 if (str.charAt(i) === '{') { 6011 ret += str.charAt(i++); 6012 while (i < str.length && str.charAt(i) !== '}') { 6013 ret += str.charAt(i++); 6014 } 6015 if (i < str.length) { 6016 ret += str.charAt(i); 6017 } 6018 } else { 6019 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 6020 } 6021 } 6022 } else { 6023 ret += this.pseudomap[str.charAt(i)] || str.charAt(i); 6024 } 6025 } 6026 if (this.lengthen) { 6027 var add; 6028 if (ret.length <= 20) { 6029 add = Math.round(ret.length / 2); 6030 } else if (ret.length > 20 && ret.length <= 40) { 6031 add = Math.round(ret.length / 3); 6032 } else { 6033 add = Math.round(ret.length / 5); 6034 } 6035 for (i = add-1; i >= 0; i--) { 6036 ret += (i % 10); 6037 } 6038 } 6039 if (this.locale.getScript() === "Hans" || this.locale.getScript() === "Hant" || 6040 this.locale.getScript() === "Hani" || 6041 this.locale.getScript() === "Hrkt" || this.locale.getScript() === "Jpan" || 6042 this.locale.getScript() === "Hira" || this.locale.getScript() === "Kana" ) { 6043 // simulate Asian languages by getting rid of all the spaces 6044 ret = ret.replace(/ /g, ""); 6045 } 6046 return ret; 6047 }, 6048 6049 /** 6050 * @private 6051 * Escape html characters in the output. 6052 */ 6053 _escapeXml: function (str) { 6054 str = str.replace(/&/g, '&'); 6055 str = str.replace(/</g, '<'); 6056 str = str.replace(/>/g, '>'); 6057 return str; 6058 }, 6059 6060 /** 6061 * @private 6062 * @param {string} str the string to unescape 6063 */ 6064 _unescapeXml: function (str) { 6065 str = str.replace(/&/g, '&'); 6066 str = str.replace(/</g, '<'); 6067 str = str.replace(/>/g, '>'); 6068 return str; 6069 }, 6070 6071 /** 6072 * @private 6073 * Create a key name out of a source string. All this does so far is 6074 * compress sequences of white space into a single space on the assumption 6075 * that this doesn't really change the meaning of the string, and therefore 6076 * all such strings that compress to the same thing should share the same 6077 * translation. 6078 * @param {null|string=} source the source string to make a key out of 6079 */ 6080 _makeKey: function (source) { 6081 if (!source) return undefined; 6082 var key = source.replace(/\s+/gm, ' '); 6083 return (this.type === "xml" || this.type === "html") ? this._unescapeXml(key) : key; 6084 }, 6085 6086 /** 6087 * @private 6088 */ 6089 _getStringSingle: function(source, key, escapeMode) { 6090 if (!source && !key) return new IString(""); 6091 6092 var trans; 6093 if (this.locale.isPseudo()) { 6094 var str = source ? source : this.map[key]; 6095 trans = this._pseudo(str || key); 6096 } else { 6097 var keyName = key || this._makeKey(source); 6098 if (typeof(this.map[keyName]) !== 'undefined') { 6099 trans = this.map[keyName]; 6100 } else if (this.missing === "pseudo") { 6101 trans = this._pseudo(source || key); 6102 } else if (this.missing === "empty") { 6103 trans = ""; 6104 } else { 6105 trans = source; 6106 } 6107 } 6108 6109 if (escapeMode && escapeMode !== "none") { 6110 if (escapeMode == "default") { 6111 escapeMode = this.type; 6112 } 6113 if (escapeMode === "xml" || escapeMode === "html") { 6114 trans = this._escapeXml(trans); 6115 } else if (escapeMode == "js" || escapeMode === "attribute") { 6116 trans = trans.replace(/'/g, "\\\'").replace(/"/g, "\\\""); 6117 } 6118 } 6119 if (trans === undefined) { 6120 return undefined; 6121 } else { 6122 var ret = new IString(trans); 6123 ret.setLocale(this.locale.getSpec(), true, this.loadParams); // no callback 6124 return ret; 6125 } 6126 }, 6127 6128 /** 6129 * Return a localized string, array, or object. This method can localize individual 6130 * strings or arrays of strings.<p> 6131 * 6132 * If the source parameter is a string, the translation of that string is looked 6133 * up and returned. If the source parameter is an array of strings, then the translation 6134 * of each of the elements of that array is looked up, and an array of translated strings 6135 * is returned. <p> 6136 * 6137 * If any string is not found in the loaded set of 6138 * resources, the original source string is returned. If the key is not given, 6139 * then the source string itself is used as the key. In the case where the 6140 * source string is used as the key, the whitespace is compressed down to 1 space 6141 * each, and the whitespace at the beginning and end of the string is trimmed.<p> 6142 * 6143 * The escape mode specifies what type of output you are escaping the returned 6144 * string for. Modes are similar to the types: 6145 * 6146 * <ul> 6147 * <li>"html" -- prevents HTML injection by escaping the characters < > and & 6148 * <li>"xml" -- currently same as "html" mode 6149 * <li>"js" -- prevents breaking Javascript syntax by backslash escaping all quote and 6150 * double-quote characters 6151 * <li>"attribute" -- meant for HTML attribute values. Currently this is the same as 6152 * "js" escape mode. 6153 * <li>"default" -- use the type parameter from the constructor as the escape mode as well 6154 * <li>"none" or undefined -- no escaping at all. 6155 * </ul> 6156 * 6157 * The type parameter of the constructor specifies what type of strings this bundle 6158 * is operating upon. This allows pseudo-translation and automatic key generation 6159 * to happen properly by telling this class how to parse the string. The escape mode 6160 * for this method is different in that it specifies how this string will be used in 6161 * the calling code and therefore how to escape it properly.<p> 6162 * 6163 * For example, a section of Javascript code may be constructing an HTML snippet in a 6164 * string to add to the web page. In this case, the type parameter in the constructor should 6165 * be "html" so that the source string can be parsed properly, but the escape mode should 6166 * be "js" so that the output string can be used in Javascript without causing syntax 6167 * errors. 6168 * 6169 * @param {?string|Array.<string>=} source the source string or strings to translate 6170 * @param {?string|Array.<string>=} key optional name of the key, if any 6171 * @param {?string=} escapeMode escape mode, if any 6172 * @return {IString|Array.<IString>|undefined} the translation of the given source/key or undefined 6173 * if the translation is not found and the source is undefined 6174 */ 6175 getString: function (source, key, escapeMode) { 6176 if (!source && !key) return new IString(""); 6177 6178 //if (typeof(source) === "object") { 6179 // TODO localize objects 6180 //} else 6181 6182 if (ilib.isArray(source)) { 6183 return source.map(ilib.bind(this, function(str) { 6184 return typeof(str) === "string" ? this._getStringSingle(str, key, escapeMode) : str; 6185 })); 6186 } else { 6187 return this._getStringSingle(source, key, escapeMode); 6188 } 6189 }, 6190 6191 /** 6192 * Return a localized string as an intrinsic Javascript String object. This does the same thing as 6193 * the getString() method, but it returns a regular Javascript string instead of 6194 * and IString instance. This means it cannot be formatted with the format() 6195 * method without being wrapped in an IString instance first. 6196 * 6197 * @param {?string|Array.<string>=} source the source string to translate 6198 * @param {?string|Array.<string>=} key optional name of the key, if any 6199 * @param {?string=} escapeMode escape mode, if any 6200 * @return {string|Array.<string>|undefined} the translation of the given source/key or undefined 6201 * if the translation is not found and the source is undefined 6202 */ 6203 getStringJS: function(source, key, escapeMode) { 6204 if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { 6205 return undefined; 6206 } 6207 //if (typeof(source) === "object") { 6208 // TODO localize objects 6209 //} else 6210 6211 if (ilib.isArray(source)) { 6212 return this.getString(source, key, escapeMode).map(function(str) { 6213 return (str && str instanceof IString) ? str.toString() : str; 6214 }); 6215 } else { 6216 var s = this.getString(source, key, escapeMode); 6217 return s ? s.toString() : undefined; 6218 } 6219 }, 6220 6221 /** 6222 * Return true if the current bundle contains a translation for the given key and 6223 * source. The 6224 * getString method will always return a string for any given key and source 6225 * combination, so it cannot be used to tell if a translation exists. Either one 6226 * or both of the source and key must be specified. If both are not specified, 6227 * this method will return false. 6228 * 6229 * @param {?string=} source source string to look up 6230 * @param {?string=} key key to look up 6231 * @return {boolean} true if this bundle contains a translation for the key, and 6232 * false otherwise 6233 */ 6234 containsKey: function(source, key) { 6235 if (typeof(source) === 'undefined' && typeof(key) === 'undefined') { 6236 return false; 6237 } 6238 6239 var keyName = key || this._makeKey(source); 6240 return typeof(this.map[keyName]) !== 'undefined'; 6241 }, 6242 6243 /** 6244 * Return the merged resources as an entire object. When loading resources for a 6245 * locale that are not just a set of translated strings, but instead an entire 6246 * structured javascript object, you can gain access to that object via this call. This method 6247 * will ensure that all the of the parts of the object are correct for the locale.<p> 6248 * 6249 * For pre-assembled data, it starts by loading <i>ilib.data[name]</i>, where 6250 * <i>name</i> is the base name for this set of resources. Then, it successively 6251 * merges objects in the base data using progressively more locale-specific data. 6252 * It loads it in this order from <i>ilib.data</i>: 6253 * 6254 * <ol> 6255 * <li> language 6256 * <li> region 6257 * <li> language_script 6258 * <li> language_region 6259 * <li> region_variant 6260 * <li> language_script_region 6261 * <li> language_region_variant 6262 * <li> language_script_region_variant 6263 * </ol> 6264 * 6265 * For dynamically loaded data, the code attempts to load the same sequence as 6266 * above, but with slash path separators instead of underscores.<p> 6267 * 6268 * Loading the resources this way allows the program to share resources between all 6269 * locales that share a common language, region, or script. As a 6270 * general rule-of-thumb, resources should be as generic as possible in order to 6271 * cover as many locales as possible. 6272 * 6273 * @return {Object} returns the object that is the basis for this resources instance 6274 */ 6275 getResObj: function () { 6276 return this.map; 6277 } 6278 }; 6279 6280 6281 6282 /*< GregorianCal.js */ 6283 /* 6284 * gregorian.js - Represent a Gregorian calendar object. 6285 * 6286 * Copyright © 2012-2015,2018, JEDLSoft 6287 * 6288 * Licensed under the Apache License, Version 2.0 (the "License"); 6289 * you may not use this file except in compliance with the License. 6290 * You may obtain a copy of the License at 6291 * 6292 * http://www.apache.org/licenses/LICENSE-2.0 6293 * 6294 * Unless required by applicable law or agreed to in writing, software 6295 * distributed under the License is distributed on an "AS IS" BASIS, 6296 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6297 * 6298 * See the License for the specific language governing permissions and 6299 * limitations under the License. 6300 */ 6301 6302 6303 /* !depends Calendar.js MathUtils.js */ 6304 6305 6306 /** 6307 * @class 6308 * Construct a new Gregorian calendar object. This class encodes information about 6309 * a Gregorian calendar.<p> 6310 * 6311 * 6312 * @constructor 6313 * @param {{noinstance:boolean}=} options 6314 * @extends Calendar 6315 */ 6316 var GregorianCal = function(options) { 6317 if (!options || !options.noinstance) { 6318 this.type = "gregorian"; 6319 } 6320 6321 if (options && typeof(options.onLoad) === "function") { 6322 options.onLoad(this); 6323 } 6324 }; 6325 6326 /** 6327 * the lengths of each month 6328 * @private 6329 * @const 6330 * @type Array.<number> 6331 */ 6332 GregorianCal.monthLengths = [ 6333 31, /* Jan */ 6334 28, /* Feb */ 6335 31, /* Mar */ 6336 30, /* Apr */ 6337 31, /* May */ 6338 30, /* Jun */ 6339 31, /* Jul */ 6340 31, /* Aug */ 6341 30, /* Sep */ 6342 31, /* Oct */ 6343 30, /* Nov */ 6344 31 /* Dec */ 6345 ]; 6346 6347 /** 6348 * Return the number of months in the given year. The number of months in a year varies 6349 * for some luni-solar calendars because in some years, an extra month is needed to extend the 6350 * days in a year to an entire solar year. The month is represented as a 1-based number 6351 * where 1=first month, 2=second month, etc. 6352 * 6353 * @param {number} year a year for which the number of months is sought 6354 * @return {number} The number of months in the given year 6355 */ 6356 GregorianCal.prototype.getNumMonths = function(year) { 6357 return 12; 6358 }; 6359 6360 /** 6361 * Return the number of days in a particular month in a particular year. This function 6362 * can return a different number for a month depending on the year because of things 6363 * like leap years. 6364 * 6365 * @param {number} month the month for which the length is sought 6366 * @param {number} year the year within which that month can be found 6367 * @return {number} the number of days within the given month in the given year 6368 */ 6369 GregorianCal.prototype.getMonLength = function(month, year) { 6370 if (month !== 2 || !this.isLeapYear(year)) { 6371 return GregorianCal.monthLengths[month-1]; 6372 } else { 6373 return 29; 6374 } 6375 }; 6376 6377 /** 6378 * Return true if the given year is a leap year in the Gregorian calendar. 6379 * The year parameter may be given as a number, or as a GregDate object. 6380 * @param {number|GregorianDate} year the year for which the leap year information is being sought 6381 * @return {boolean} true if the given year is a leap year 6382 */ 6383 GregorianCal.prototype.isLeapYear = function(year) { 6384 var y = (typeof(year) === 'number' ? year : year.getYears()); 6385 var centuries = MathUtils.mod(y, 400); 6386 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 6387 }; 6388 6389 /** 6390 * Return the type of this calendar. 6391 * 6392 * @return {string} the name of the type of this calendar 6393 */ 6394 GregorianCal.prototype.getType = function() { 6395 return this.type; 6396 }; 6397 6398 /* register this calendar for the factory method */ 6399 Calendar._constructors["gregorian"] = GregorianCal; 6400 6401 6402 6403 /*< RataDie.js */ 6404 /* 6405 * ratadie.js - Represent the RD date number in the calendar 6406 * 6407 * Copyright © 2014-2015, JEDLSoft 6408 * 6409 * Licensed under the Apache License, Version 2.0 (the "License"); 6410 * you may not use this file except in compliance with the License. 6411 * You may obtain a copy of the License at 6412 * 6413 * http://www.apache.org/licenses/LICENSE-2.0 6414 * 6415 * Unless required by applicable law or agreed to in writing, software 6416 * distributed under the License is distributed on an "AS IS" BASIS, 6417 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6418 * 6419 * See the License for the specific language governing permissions and 6420 * limitations under the License. 6421 */ 6422 6423 /* !depends 6424 JulianDay.js 6425 MathUtils.js 6426 JSUtils.js 6427 */ 6428 6429 6430 /** 6431 * @class 6432 * Construct a new RD date number object. The constructor parameters can 6433 * contain any of the following properties: 6434 * 6435 * <ul> 6436 * <li><i>unixtime<i> - sets the time of this instance according to the given 6437 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 6438 * 6439 * <li><i>julianday</i> - sets the time of this instance according to the given 6440 * Julian Day instance or the Julian Day given as a float 6441 * 6442 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 6443 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 6444 * linear count of years since the beginning of the epoch, much like other calendars. This linear 6445 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 6446 * to 60 and treated as if it were a year in the regular 60-year cycle. 6447 * 6448 * <li><i>year</i> - any integer, including 0 6449 * 6450 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 6451 * 6452 * <li><i>day</i> - 1 to 31 6453 * 6454 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 6455 * is always done with an unambiguous 24 hour representation 6456 * 6457 * <li><i>minute</i> - 0 to 59 6458 * 6459 * <li><i>second</i> - 0 to 59 6460 * 6461 * <li><i>millisecond</i> - 0 to 999 6462 * 6463 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 6464 * the parts or specify the minutes, seconds, and milliseconds, but not both. This is only used 6465 * in the Hebrew calendar. 6466 * 6467 * <li><i>minute</i> - 0 to 59 6468 * 6469 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 6470 * </ul> 6471 * 6472 * If the constructor is called with another date instance instead of 6473 * a parameter block, the other instance acts as a parameter block and its 6474 * settings are copied into the current instance.<p> 6475 * 6476 * If the constructor is called with no arguments at all or if none of the 6477 * properties listed above are present, then the RD is calculate based on 6478 * the current date at the time of instantiation. <p> 6479 * 6480 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 6481 * specified in the params, it is assumed that they have the smallest possible 6482 * value in the range for the property (zero or one).<p> 6483 * 6484 * 6485 * @private 6486 * @constructor 6487 * @param {Object=} params parameters that govern the settings and behaviour of this RD date 6488 */ 6489 var RataDie = function(params) { 6490 if (params) { 6491 if (typeof(params.date) !== 'undefined') { 6492 // accept JS Date classes or strings 6493 var date = params.date; 6494 if (!(JSUtils.isDate(date))) { 6495 date = new Date(date); // maybe a string initializer? 6496 } 6497 this._setTime(date.getTime()); 6498 } else if (typeof(params.unixtime) !== 'undefined') { 6499 this._setTime(parseInt(params.unixtime, 10)); 6500 } else if (typeof(params.julianday) !== 'undefined') { 6501 // JD time is defined to be UTC 6502 this._setJulianDay(parseFloat(params.julianday)); 6503 } else if (params.year || params.month || params.day || params.hour || 6504 params.minute || params.second || params.millisecond || params.parts || params.cycle) { 6505 this._setDateComponents(params); 6506 } else if (typeof(params.rd) !== 'undefined') { 6507 /** 6508 * @type {number} the Rata Die number of this date for this calendar type 6509 */ 6510 this.rd = (typeof(params.rd) === 'object' && params.rd instanceof RataDie) ? params.rd.rd : params.rd; 6511 } 6512 } 6513 6514 if (typeof(this.rd) === 'undefined' || isNaN(this.rd)) { 6515 var now = new Date(); 6516 this._setTime(now.getTime()); 6517 } 6518 }; 6519 6520 /** 6521 * @private 6522 * @const 6523 * @type {number} 6524 */ 6525 RataDie.gregorianEpoch = 1721424.5; 6526 6527 RataDie.prototype = { 6528 /** 6529 * @protected 6530 * @type {number} 6531 * the difference between a zero Julian day and the zero Gregorian date. 6532 */ 6533 epoch: RataDie.gregorianEpoch, 6534 6535 /** 6536 * Set the RD of this instance according to the given unix time. Unix time is 6537 * the number of milliseconds since midnight on Jan 1, 1970. 6538 * 6539 * @protected 6540 * @param {number} millis the unix time to set this date to in milliseconds 6541 */ 6542 _setTime: function(millis) { 6543 // 2440587.5 is the julian day of midnight Jan 1, 1970, UTC (Gregorian) 6544 this._setJulianDay(2440587.5 + millis / 86400000); 6545 }, 6546 6547 /** 6548 * Set the date of this instance using a Julian Day. 6549 * @protected 6550 * @param {number} date the Julian Day to use to set this date 6551 */ 6552 _setJulianDay: function (date) { 6553 var jd = (typeof(date) === 'number') ? new JulianDay(date) : date; 6554 // round to the nearest millisecond 6555 this.rd = MathUtils.halfup((jd.getDate() - this.epoch) * 86400000) / 86400000; 6556 }, 6557 6558 /** 6559 * Return the rd number of the particular day of the week on or before the 6560 * given rd. eg. The Sunday on or before the given rd. 6561 * @protected 6562 * @param {number} rd the rata die date of the reference date 6563 * @param {number} dayOfWeek the day of the week that is being sought relative 6564 * to the current date 6565 * @return {number} the rd of the day of the week 6566 */ 6567 _onOrBefore: function(rd, dayOfWeek) { 6568 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 2, 7); 6569 }, 6570 6571 /** 6572 * Return the rd number of the particular day of the week on or before the current rd. 6573 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 6574 * happens in wall time instead of UTC. UTC time may be a day before or day behind 6575 * wall time, so it it would give the wrong day of the week if this calculation was 6576 * done in UTC time when the caller really wanted wall time. Even though the calculation 6577 * may be done in wall time, the return value is nonetheless always given in UTC. 6578 * @param {number} dayOfWeek the day of the week that is being sought relative 6579 * to the current date 6580 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 6581 * not given 6582 * @return {number} the rd of the day of the week 6583 */ 6584 onOrBefore: function(dayOfWeek, offset) { 6585 offset = offset || 0; 6586 return this._onOrBefore(this.rd + offset, dayOfWeek) - offset; 6587 }, 6588 6589 /** 6590 * Return the rd number of the particular day of the week on or before the current rd. 6591 * eg. The Sunday on or before the current rd. If the offset is given, the calculation 6592 * happens in wall time instead of UTC. UTC time may be a day before or day behind 6593 * wall time, so it it would give the wrong day of the week if this calculation was 6594 * done in UTC time when the caller really wanted wall time. Even though the calculation 6595 * may be done in wall time, the return value is nonetheless always given in UTC. 6596 * @param {number} dayOfWeek the day of the week that is being sought relative 6597 * to the reference date 6598 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 6599 * not given 6600 * @return {number} the day of the week 6601 */ 6602 onOrAfter: function(dayOfWeek, offset) { 6603 offset = offset || 0; 6604 return this._onOrBefore(this.rd+6+offset, dayOfWeek) - offset; 6605 }, 6606 6607 /** 6608 * Return the rd number of the particular day of the week before the current rd. 6609 * eg. The Sunday before the current rd. If the offset is given, the calculation 6610 * happens in wall time instead of UTC. UTC time may be a day before or day behind 6611 * wall time, so it it would give the wrong day of the week if this calculation was 6612 * done in UTC time when the caller really wanted wall time. Even though the calculation 6613 * may be done in wall time, the return value is nonetheless always given in UTC. 6614 * @param {number} dayOfWeek the day of the week that is being sought relative 6615 * to the reference date 6616 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 6617 * not given 6618 * @return {number} the day of the week 6619 */ 6620 before: function(dayOfWeek, offset) { 6621 offset = offset || 0; 6622 return this._onOrBefore(this.rd-1+offset, dayOfWeek) - offset; 6623 }, 6624 6625 /** 6626 * Return the rd number of the particular day of the week after the current rd. 6627 * eg. The Sunday after the current rd. If the offset is given, the calculation 6628 * happens in wall time instead of UTC. UTC time may be a day before or day behind 6629 * wall time, so it it would give the wrong day of the week if this calculation was 6630 * done in UTC time when the caller really wanted wall time. Even though the calculation 6631 * may be done in wall time, the return value is nonetheless always given in UTC. 6632 * @param {number} dayOfWeek the day of the week that is being sought relative 6633 * to the reference date 6634 * @param {number=} offset RD offset for the time zone. Zero is assumed if this param is 6635 * not given 6636 * @return {number} the day of the week 6637 */ 6638 after: function(dayOfWeek, offset) { 6639 offset = offset || 0; 6640 return this._onOrBefore(this.rd+7+offset, dayOfWeek) - offset; 6641 }, 6642 6643 /** 6644 * Return the unix time equivalent to this Gregorian date instance. Unix time is 6645 * the number of milliseconds since midnight on Jan 1, 1970 UTC. This method only 6646 * returns a valid number for dates between midnight, Jan 1, 1970 and 6647 * Jan 19, 2038 at 3:14:07am when the unix time runs out. If this instance 6648 * encodes a date outside of that range, this method will return -1. 6649 * 6650 * @return {number} a number giving the unix time, or -1 if the date is outside the 6651 * valid unix time range 6652 */ 6653 getTime: function() { 6654 // earlier than Jan 1, 1970 6655 // or later than Jan 19, 2038 at 3:14:07am 6656 var jd = this.getJulianDay(); 6657 if (jd < 2440587.5 || jd > 2465442.634803241) { 6658 return -1; 6659 } 6660 6661 // avoid the rounding errors in the floating point math by only using 6662 // the whole days from the rd, and then calculating the milliseconds directly 6663 return Math.round((jd - 2440587.5) * 86400000); 6664 }, 6665 6666 /** 6667 * Return the extended unix time equivalent to this Gregorian date instance. Unix time is 6668 * the number of milliseconds since midnight on Jan 1, 1970 UTC. Traditionally unix time 6669 * (or the type "time_t" in C/C++) is only encoded with a unsigned 32 bit integer, and thus 6670 * runs out on Jan 19, 2038. However, most Javascript engines encode numbers well above 6671 * 32 bits and the Date object allows you to encode up to 100 million days worth of time 6672 * after Jan 1, 1970, and even more interestingly 100 million days worth of time before 6673 * Jan 1, 1970 as well. This method returns the number of milliseconds in that extended 6674 * range. If this instance encodes a date outside of that range, this method will return 6675 * NaN. 6676 * 6677 * @return {number} a number giving the extended unix time, or NaN if the date is outside 6678 * the valid extended unix time range 6679 */ 6680 getTimeExtended: function() { 6681 var jd = this.getJulianDay(); 6682 6683 // test if earlier than Jan 1, 1970 - 100 million days 6684 // or later than Jan 1, 1970 + 100 million days 6685 if (jd < -97559412.5 || jd > 102440587.5) { 6686 return NaN; 6687 } 6688 6689 // avoid the rounding errors in the floating point math by only using 6690 // the whole days from the rd, and then calculating the milliseconds directly 6691 return Math.round((jd - 2440587.5) * 86400000); 6692 }, 6693 6694 /** 6695 * Return the Julian Day equivalent to this calendar date as a number. 6696 * This returns the julian day in UTC. 6697 * 6698 * @return {number} the julian date equivalent of this date 6699 */ 6700 getJulianDay: function() { 6701 return this.rd + this.epoch; 6702 }, 6703 6704 /** 6705 * Return the Rata Die (fixed day) number of this RD date. 6706 * 6707 * @return {number} the rd date as a number 6708 */ 6709 getRataDie: function() { 6710 return this.rd; 6711 } 6712 }; 6713 6714 6715 /*< GregRataDie.js */ 6716 /* 6717 * GregRataDie.js - Represent the RD date number in the Gregorian calendar 6718 * 6719 * Copyright © 2014-2015, JEDLSoft 6720 * 6721 * Licensed under the Apache License, Version 2.0 (the "License"); 6722 * you may not use this file except in compliance with the License. 6723 * You may obtain a copy of the License at 6724 * 6725 * http://www.apache.org/licenses/LICENSE-2.0 6726 * 6727 * Unless required by applicable law or agreed to in writing, software 6728 * distributed under the License is distributed on an "AS IS" BASIS, 6729 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6730 * 6731 * See the License for the specific language governing permissions and 6732 * limitations under the License. 6733 */ 6734 6735 /* !depends 6736 GregorianCal.js 6737 RataDie.js 6738 MathUtils.js 6739 */ 6740 6741 6742 /** 6743 * @class 6744 * Construct a new Gregorian RD date number object. The constructor parameters can 6745 * contain any of the following properties: 6746 * 6747 * <ul> 6748 * <li><i>unixtime<i> - sets the time of this instance according to the given 6749 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 6750 * 6751 * <li><i>julianday</i> - sets the time of this instance according to the given 6752 * Julian Day instance or the Julian Day given as a float 6753 * 6754 * <li><i>year</i> - any integer, including 0 6755 * 6756 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 6757 * 6758 * <li><i>day</i> - 1 to 31 6759 * 6760 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 6761 * is always done with an unambiguous 24 hour representation 6762 * 6763 * <li><i>minute</i> - 0 to 59 6764 * 6765 * <li><i>second</i> - 0 to 59 6766 * 6767 * <li><i>millisecond</i> - 0 to 999 6768 * 6769 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 6770 * </ul> 6771 * 6772 * If the constructor is called with another Gregorian date instance instead of 6773 * a parameter block, the other instance acts as a parameter block and its 6774 * settings are copied into the current instance.<p> 6775 * 6776 * If the constructor is called with no arguments at all or if none of the 6777 * properties listed above are present, then the RD is calculate based on 6778 * the current date at the time of instantiation. <p> 6779 * 6780 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 6781 * specified in the params, it is assumed that they have the smallest possible 6782 * value in the range for the property (zero or one).<p> 6783 * 6784 * 6785 * @private 6786 * @constructor 6787 * @extends RataDie 6788 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian RD date 6789 */ 6790 var GregRataDie = function(params) { 6791 this.cal = params && params.cal || new GregorianCal(); 6792 /** @type {number|undefined} */ 6793 this.rd = NaN; 6794 RataDie.call(this, params); 6795 }; 6796 6797 GregRataDie.prototype = new RataDie(); 6798 GregRataDie.prototype.parent = RataDie; 6799 GregRataDie.prototype.constructor = GregRataDie; 6800 6801 /** 6802 * the cumulative lengths of each month, for a non-leap year 6803 * @private 6804 * @const 6805 * @type Array.<number> 6806 */ 6807 GregRataDie.cumMonthLengths = [ 6808 0, /* Jan */ 6809 31, /* Feb */ 6810 59, /* Mar */ 6811 90, /* Apr */ 6812 120, /* May */ 6813 151, /* Jun */ 6814 181, /* Jul */ 6815 212, /* Aug */ 6816 243, /* Sep */ 6817 273, /* Oct */ 6818 304, /* Nov */ 6819 334, /* Dec */ 6820 365 6821 ]; 6822 6823 /** 6824 * the cumulative lengths of each month, for a leap year 6825 * @private 6826 * @const 6827 * @type Array.<number> 6828 */ 6829 GregRataDie.cumMonthLengthsLeap = [ 6830 0, /* Jan */ 6831 31, /* Feb */ 6832 60, /* Mar */ 6833 91, /* Apr */ 6834 121, /* May */ 6835 152, /* Jun */ 6836 182, /* Jul */ 6837 213, /* Aug */ 6838 244, /* Sep */ 6839 274, /* Oct */ 6840 305, /* Nov */ 6841 335, /* Dec */ 6842 366 6843 ]; 6844 6845 /** 6846 * Calculate the Rata Die (fixed day) number of the given date. 6847 * 6848 * @private 6849 * @param {Object} date the date components to calculate the RD from 6850 */ 6851 GregRataDie.prototype._setDateComponents = function(date) { 6852 var year = parseInt(date.year, 10) || 0; 6853 var month = parseInt(date.month, 10) || 1; 6854 var day = parseInt(date.day, 10) || 1; 6855 var hour = parseInt(date.hour, 10) || 0; 6856 var minute = parseInt(date.minute, 10) || 0; 6857 var second = parseInt(date.second, 10) || 0; 6858 var millisecond = parseInt(date.millisecond, 10) || 0; 6859 6860 var years = 365 * (year - 1) + 6861 Math.floor((year-1)/4) - 6862 Math.floor((year-1)/100) + 6863 Math.floor((year-1)/400); 6864 6865 var dayInYear = (month > 1 ? GregRataDie.cumMonthLengths[month-1] : 0) + 6866 day + 6867 (GregorianCal.prototype.isLeapYear.call(this.cal, year) && month > 2 ? 1 : 0); 6868 var rdtime = (hour * 3600000 + 6869 minute * 60000 + 6870 second * 1000 + 6871 millisecond) / 6872 86400000; 6873 /* 6874 debug("getRataDie: converting " + JSON.stringify(this)); 6875 debug("getRataDie: year is " + years); 6876 debug("getRataDie: day in year is " + dayInYear); 6877 debug("getRataDie: rdtime is " + rdtime); 6878 debug("getRataDie: rd is " + (years + dayInYear + rdtime)); 6879 */ 6880 6881 /** 6882 * @type {number|undefined} the RD number of this Gregorian date 6883 */ 6884 this.rd = years + dayInYear + rdtime; 6885 }; 6886 6887 /** 6888 * Return the rd number of the particular day of the week on or before the 6889 * given rd. eg. The Sunday on or before the given rd. 6890 * @private 6891 * @param {number} rd the rata die date of the reference date 6892 * @param {number} dayOfWeek the day of the week that is being sought relative 6893 * to the current date 6894 * @return {number} the rd of the day of the week 6895 */ 6896 GregRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 6897 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 6898 }; 6899 6900 6901 /*< TimeZone.js */ 6902 /* 6903 * TimeZone.js - Definition of a time zone class 6904 * 6905 * Copyright © 2012-2015, JEDLSoft 6906 * 6907 * Licensed under the Apache License, Version 2.0 (the "License"); 6908 * you may not use this file except in compliance with the License. 6909 * You may obtain a copy of the License at 6910 * 6911 * http://www.apache.org/licenses/LICENSE-2.0 6912 * 6913 * Unless required by applicable law or agreed to in writing, software 6914 * distributed under the License is distributed on an "AS IS" BASIS, 6915 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 6916 * 6917 * See the License for the specific language governing permissions and 6918 * limitations under the License. 6919 */ 6920 6921 /* 6922 !depends 6923 ilib.js 6924 Locale.js 6925 LocaleInfo.js 6926 Utils.js 6927 MathUtils.js 6928 JSUtils.js 6929 GregRataDie.js 6930 IString.js 6931 CalendarFactory.js 6932 */ 6933 6934 // !data localeinfo zoneinfo 6935 6936 6937 6938 6939 /** 6940 * @class 6941 * Create a time zone instance. 6942 * 6943 * This class reports and transforms 6944 * information about particular time zones.<p> 6945 * 6946 * The options parameter may contain any of the following properties: 6947 * 6948 * <ul> 6949 * <li><i>id</i> - The id of the requested time zone such as "Europe/London" or 6950 * "America/Los_Angeles". These are taken from the IANA time zone database. (See 6951 * http://www.iana.org/time-zones for more information.) <p> 6952 * 6953 * There is one special 6954 * time zone that is not taken from the IANA database called simply "local". In 6955 * this case, this class will attempt to discover the current time zone and 6956 * daylight savings time settings by calling standard Javascript classes to 6957 * determine the offsets from UTC. 6958 * 6959 * <li><i>locale</i> - The locale for this time zone. 6960 * 6961 * <li><i>offset</i> - Choose the time zone based on the offset from UTC given in 6962 * number of minutes (negative is west, positive is east). 6963 * 6964 * <li><i>onLoad</i> - a callback function to call when the data is fully 6965 * loaded. When the onLoad option is given, this class will attempt to 6966 * load any missing locale data using the ilib loader callback. 6967 * When the data is loaded, the onLoad function is called with the current 6968 * instance as a parameter. 6969 * 6970 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 6971 * asynchronously. If this option is given as "false", then the "onLoad" 6972 * callback must be given, as the instance returned from this constructor will 6973 * not be usable for a while. 6974 * 6975 * <li><i>loadParams</i> - an object containing parameters to pass to the 6976 * loader callback function when locale data is missing. The parameters are not 6977 * interpretted or modified in any way. They are simply passed along. The object 6978 * may contain any property/value pairs as long as the calling code is in 6979 * agreement with the loader callback function as to what those parameters mean. 6980 * </ul> 6981 * 6982 * There is currently no way in the ECMAscript 6983 * standard to tell which exact time zone is currently in use. Choosing the 6984 * id "locale" or specifying an explicit offset will not give a specific time zone, 6985 * as it is impossible to tell with certainty which zone the offsets 6986 * match.<p> 6987 * 6988 * When the id "local" is given or the offset option is specified, this class will 6989 * have the following behaviours: 6990 * <ul> 6991 * <li>The display name will always be given as the RFC822 style, no matter what 6992 * style is requested 6993 * <li>The id will also be returned as the RFC822 style display name 6994 * <li>When the offset is explicitly given, this class will assume the time zone 6995 * does not support daylight savings time, and the offsets will be calculated 6996 * the same way year round. 6997 * <li>When the offset is explicitly given, the inDaylightSavings() method will 6998 * always return false. 6999 * <li>When the id "local" is given, this class will attempt to determine the 7000 * daylight savings time settings by examining the offset from UTC on Jan 1 7001 * and June 1 of the current year. If they are different, this class assumes 7002 * that the local time zone uses DST. When the offset for a particular date is 7003 * requested, it will use the built-in Javascript support to determine the 7004 * offset for that date. 7005 * </ul> 7006 * 7007 * If a more specific time zone is 7008 * needed with display names and known start/stop times for DST, use the "id" 7009 * property instead to specify the time zone exactly. You can perhaps ask the 7010 * user which time zone they prefer so that your app does not need to guess.<p> 7011 * 7012 * If the id and the offset are both not given, the default time zone for the 7013 * locale is retrieved from 7014 * the locale info. If the locale is not specified, the default locale for the 7015 * library is used.<p> 7016 * 7017 * Because this class was designed for use in web sites, and the vast majority 7018 * of dates and times being formatted are recent date/times, this class is simplified 7019 * by not implementing historical time zones. That is, when governments change the 7020 * time zone rules for a particular zone, only the latest such rule is implemented 7021 * in this class. That means that determining the offset for a date that is prior 7022 * to the last change may give the wrong result. Historical time zone calculations 7023 * may be implemented in a later version of iLib if there is enough demand for it, 7024 * but it would entail a much larger set of time zone data that would have to be 7025 * loaded. 7026 * 7027 * 7028 * @constructor 7029 * @param {Object} options Options guiding the construction of this time zone instance 7030 */ 7031 var TimeZone = function(options) { 7032 this.sync = true; 7033 this.locale = new Locale(); 7034 this.isLocal = false; 7035 7036 if (options) { 7037 if (options.locale) { 7038 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 7039 } 7040 7041 if (options.id) { 7042 var id = options.id.toString(); 7043 if (id === 'local') { 7044 this.isLocal = true; 7045 7046 // use standard Javascript Date to figure out the time zone offsets 7047 var now = new Date(), 7048 jan1 = new Date(now.getFullYear(), 0, 1), // months in std JS Date object are 0-based 7049 jun1 = new Date(now.getFullYear(), 5, 1); 7050 7051 // Javascript's method returns the offset backwards, so we have to 7052 // take the negative to get the correct offset 7053 this.offsetJan1 = -jan1.getTimezoneOffset(); 7054 this.offsetJun1 = -jun1.getTimezoneOffset(); 7055 // the offset of the standard time for the time zone is always the one that is closest 7056 // to negative infinity of the two, no matter whether you are in the northern or southern 7057 // hemisphere, east or west 7058 this.offset = Math.min(this.offsetJan1, this.offsetJun1); 7059 } 7060 this.id = id; 7061 } else if (options.offset) { 7062 this.offset = (typeof(options.offset) === 'string') ? parseInt(options.offset, 10) : options.offset; 7063 this.id = this.getDisplayName(undefined, undefined); 7064 } 7065 7066 if (typeof(options.sync) !== 'undefined') { 7067 this.sync = !!options.sync; 7068 } 7069 7070 this.loadParams = options.loadParams; 7071 this.onLoad = options.onLoad; 7072 } 7073 7074 //console.log("timezone: locale is " + this.locale); 7075 7076 if (!this.id) { 7077 new LocaleInfo(this.locale, { 7078 sync: this.sync, 7079 loadParams: this.loadParams, 7080 onLoad: ilib.bind(this, function (li) { 7081 this.id = li.getTimeZone() || "Etc/UTC"; 7082 this._loadtzdata(); 7083 }) 7084 }); 7085 } else { 7086 this._loadtzdata(); 7087 } 7088 7089 //console.log("localeinfo is: " + JSON.stringify(this.locinfo)); 7090 //console.log("id is: " + JSON.stringify(this.id)); 7091 }; 7092 7093 /* 7094 * Explanation of the compressed time zone info properties. 7095 * { 7096 * "o": "8:0", // offset from UTC 7097 * "f": "W{c}T", // standard abbreviation. For time zones that observe DST, the {c} replacement is replaced with the 7098 * // letter in the e.c or s.c properties below 7099 * "e": { // info about the end of DST 7100 * "j": 78322.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 7101 * // "t" properties, but not both sets. 7102 * "m": 3, // month that it ends 7103 * "r": "l0", // rule for the day it ends "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 7104 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 7105 * "t": "2:0", // time of day that the DST turns off, hours:minutes 7106 * "c": "S" // character to replace into the abbreviation for standard time 7107 * }, 7108 * "s": { // info about the start of DST 7109 * "j": 78189.5 // Julian day when the transition happens. Either specify the "j" property or all of the "m", "r", and 7110 * // "t" properties, but not both sets. 7111 * "m": 10, // month that it starts 7112 * "r": "l0", // rule for the day it starts "l" = "last", numbers are Sun=0 through Sat=6. Other syntax is "0>7". 7113 * // This means the 0-day (Sun) after the 7th of the month. Other possible operators are <, >, <=, >= 7114 * "t": "2:0", // time of day that the DST turns on, hours:minutes 7115 * "v": "1:0", // amount of time saved in hours:minutes 7116 * "c": "D" // character to replace into the abbreviation for daylight time 7117 * }, 7118 * "c": "AU", // ISO code for the country that contains this time zone 7119 * "n": "W. Australia {c} Time" 7120 * // long English name of the zone. The {c} replacement is for the word "Standard" or "Daylight" as appropriate 7121 * } 7122 */ 7123 TimeZone.prototype._loadtzdata = function () { 7124 var zoneName = this.id.replace(/-/g, "m").replace(/\+/g, "p"); 7125 // console.log("id is: " + JSON.stringify(this.id)); 7126 // console.log("zoneinfo is: " + JSON.stringify(ilib.data.zoneinfo[zoneName])); 7127 if (!ilib.data.zoneinfo[zoneName] && typeof(this.offset) === 'undefined') { 7128 Utils.loadData({ 7129 object: "TimeZone", 7130 nonlocale: true, // locale independent 7131 name: "zoneinfo/" + this.id + ".json", 7132 sync: this.sync, 7133 loadParams: this.loadParams, 7134 callback: ilib.bind(this, function (tzdata) { 7135 if (tzdata && !JSUtils.isEmpty(tzdata)) { 7136 ilib.data.zoneinfo[zoneName] = tzdata; 7137 } 7138 this._initZone(zoneName); 7139 }) 7140 }); 7141 } else { 7142 this._initZone(zoneName); 7143 } 7144 }; 7145 7146 TimeZone.prototype._initZone = function(zoneName) { 7147 /** 7148 * @private 7149 * @type {{o:string,f:string,e:Object.<{m:number,r:string,t:string,z:string}>,s:Object.<{m:number,r:string,t:string,z:string,v:string,c:string}>,c:string,n:string}} 7150 */ 7151 this.zone = ilib.data.zoneinfo[zoneName]; 7152 if (!this.zone && typeof(this.offset) === 'undefined') { 7153 this.id = "Etc/UTC"; 7154 this.zone = ilib.data.zoneinfo[this.id]; 7155 } 7156 7157 this._calcDSTSavings(); 7158 7159 if (typeof(this.offset) === 'undefined' && this.zone.o) { 7160 var offsetParts = this._offsetStringToObj(this.zone.o); 7161 /** 7162 * @private 7163 * @type {number} raw offset from UTC without DST, in minutes 7164 */ 7165 this.offset = (Math.abs(offsetParts.h || 0) * 60 + (offsetParts.m || 0)) * MathUtils.signum(offsetParts.h || 0); 7166 } 7167 7168 if (this.onLoad && typeof(this.onLoad) === 'function') { 7169 this.onLoad(this); 7170 } 7171 }; 7172 7173 /** @private */ 7174 TimeZone._marshallIds = function (country, sync, callback) { 7175 var tz, ids = []; 7176 7177 if (!country) { 7178 // local is a special zone meaning "the local time zone according to the JS engine we are running upon" 7179 ids.push("local"); 7180 for (tz in ilib.data.timezones) { 7181 if (ilib.data.timezones[tz]) { 7182 ids.push(ilib.data.timezones[tz]); 7183 } 7184 } 7185 if (typeof(callback) === 'function') { 7186 callback(ids); 7187 } 7188 } else { 7189 if (!ilib.data.zoneinfo.zonetab) { 7190 Utils.loadData({ 7191 object: "TimeZone", 7192 nonlocale: true, // locale independent 7193 name: "zoneinfo/zonetab.json", 7194 sync: sync, 7195 callback: ilib.bind(this, function (tzdata) { 7196 if (tzdata) { 7197 ilib.data.zoneinfo.zonetab = tzdata; 7198 } 7199 7200 ids = ilib.data.zoneinfo.zonetab[country]; 7201 7202 if (typeof(callback) === 'function') { 7203 callback(ids); 7204 } 7205 }) 7206 }); 7207 } else { 7208 ids = ilib.data.zoneinfo.zonetab[country]; 7209 if (typeof(callback) === 'function') { 7210 callback(ids); 7211 } 7212 } 7213 } 7214 7215 return ids; 7216 }; 7217 7218 /** 7219 * Return an array of available zone ids that the constructor knows about. 7220 * The country parameter is optional. If it is not given, all time zones will 7221 * be returned. If it specifies a country code, then only time zones for that 7222 * country will be returned. 7223 * 7224 * @param {string|undefined} country country code for which time zones are being sought 7225 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 7226 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 7227 * @return {Array.<string>} an array of zone id strings 7228 */ 7229 TimeZone.getAvailableIds = function (country, sync, onLoad) { 7230 var tz, ids = []; 7231 7232 if (typeof(sync) !== 'boolean') { 7233 sync = true; 7234 } 7235 7236 if (ilib.data.timezones.length === 0) { 7237 if (typeof(ilib._load) !== 'undefined' && typeof(ilib._load.listAvailableFiles) === 'function') { 7238 ilib._load.listAvailableFiles(sync, function(hash) { 7239 for (var dir in hash) { 7240 var files = hash[dir]; 7241 if (ilib.isArray(files)) { 7242 files.forEach(function (filename) { 7243 if (filename && filename.match(/^zoneinfo/)) { 7244 ilib.data.timezones.push(filename.replace(/^zoneinfo\//, "").replace(/\.json$/, "")); 7245 } 7246 }); 7247 } 7248 } 7249 ids = TimeZone._marshallIds(country, sync, onLoad); 7250 }); 7251 } else { 7252 for (tz in ilib.data.zoneinfo) { 7253 if (ilib.data.zoneinfo[tz]) { 7254 ilib.data.timezones.push(tz); 7255 } 7256 } 7257 ids = TimeZone._marshallIds(country, sync, onLoad); 7258 } 7259 } else { 7260 ids = TimeZone._marshallIds(country, sync, onLoad); 7261 } 7262 7263 return ids; 7264 }; 7265 7266 /** 7267 * Return the id used to uniquely identify this time zone. 7268 * @return {string} a unique id for this time zone 7269 */ 7270 TimeZone.prototype.getId = function () { 7271 return this.id.toString(); 7272 }; 7273 7274 /** 7275 * Return the abbreviation that is used for the current time zone on the given date. 7276 * The date may be in DST or during standard time, and many zone names have different 7277 * abbreviations depending on whether or not the date is falls within DST.<p> 7278 * 7279 * There are two styles that are supported: 7280 * 7281 * <ol> 7282 * <li>standard - returns the 3 to 5 letter abbreviation of the time zone name such 7283 * as "CET" for "Central European Time" or "PDT" for "Pacific Daylight Time" 7284 * <li>rfc822 - returns an RFC 822 style time zone specifier, which specifies more 7285 * explicitly what the offset is from UTC 7286 * <li>long - returns the long name of the zone in English 7287 * </ol> 7288 * 7289 * @param {IDate=} date a date to determine if it is in daylight time or standard time 7290 * @param {string=} style one of "standard" or "rfc822". Default if not specified is "standard" 7291 * @return {string} the name of the time zone, abbreviated according to the style 7292 */ 7293 TimeZone.prototype.getDisplayName = function (date, style) { 7294 var temp; 7295 style = (this.isLocal || typeof(this.zone) === 'undefined') ? "rfc822" : (style || "standard"); 7296 switch (style) { 7297 default: 7298 case 'standard': 7299 if (this.zone.f && this.zone.f !== "zzz") { 7300 if (this.zone.f.indexOf("{c}") !== -1) { 7301 var letter = ""; 7302 letter = this.inDaylightTime(date) ? this.zone.s && this.zone.s.c : this.zone.e && this.zone.e.c; 7303 temp = new IString(this.zone.f); 7304 return temp.format({c: letter || ""}); 7305 } 7306 return this.zone.f; 7307 } 7308 temp = "GMT" + this.zone.o; 7309 if (this.inDaylightTime(date)) { 7310 temp += "+" + this.zone.s.v; 7311 } 7312 return temp; 7313 7314 case 'rfc822': 7315 var offset = this.getOffset(date), // includes the DST if applicable 7316 ret = "UTC", 7317 hour = offset.h || 0, 7318 minute = offset.m || 0; 7319 7320 if (hour !== 0) { 7321 ret += (hour > 0) ? "+" : "-"; 7322 if (Math.abs(hour) < 10) { 7323 ret += "0"; 7324 } 7325 ret += (hour < 0) ? -hour : hour; 7326 if (minute < 10) { 7327 ret += "0"; 7328 } 7329 ret += minute; 7330 } 7331 return ret; 7332 7333 case 'long': 7334 if (this.zone.n) { 7335 if (this.zone.n.indexOf("{c}") !== -1) { 7336 var str = this.inDaylightTime(date) ? "Daylight" : "Standard"; 7337 temp = new IString(this.zone.n); 7338 return temp.format({c: str || ""}); 7339 } 7340 return this.zone.n; 7341 } 7342 temp = "GMT" + this.zone.o; 7343 if (this.inDaylightTime(date)) { 7344 temp += "+" + this.zone.s.v; 7345 } 7346 return temp; 7347 } 7348 }; 7349 7350 /** 7351 * Convert the offset string to an object with an h, m, and possibly s property 7352 * to indicate the hours, minutes, and seconds. 7353 * 7354 * @private 7355 * @param {string} str the offset string to convert to an object 7356 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset for the zone at 7357 * the given date/time, in hours, minutes, and seconds 7358 */ 7359 TimeZone.prototype._offsetStringToObj = function (str) { 7360 var offsetParts = (typeof(str) === 'string') ? str.split(":") : [], 7361 ret = {h:0}, 7362 temp; 7363 7364 if (offsetParts.length > 0) { 7365 ret.h = parseInt(offsetParts[0], 10); 7366 if (offsetParts.length > 1) { 7367 temp = parseInt(offsetParts[1], 10); 7368 if (temp) { 7369 ret.m = temp; 7370 } 7371 if (offsetParts.length > 2) { 7372 temp = parseInt(offsetParts[2], 10); 7373 if (temp) { 7374 ret.s = temp; 7375 } 7376 } 7377 } 7378 } 7379 7380 return ret; 7381 }; 7382 7383 /** 7384 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 7385 * time is in effect at the given date/time, this method will return the offset value 7386 * adjusted by the amount of daylight saving. 7387 * @param {IDate=} date the date for which the offset is needed 7388 * @return {Object.<{h:number,m:number}>} an object giving the offset for the zone at 7389 * the given date/time, in hours, minutes, and seconds 7390 */ 7391 TimeZone.prototype.getOffset = function (date) { 7392 if (!date) { 7393 return this.getRawOffset(); 7394 } 7395 var offset = this.getOffsetMillis(date)/60000; 7396 7397 var hours = MathUtils.down(offset/60), 7398 minutes = Math.abs(offset) - Math.abs(hours)*60; 7399 7400 var ret = { 7401 h: hours 7402 }; 7403 if (minutes != 0) { 7404 ret.m = minutes; 7405 } 7406 return ret; 7407 }; 7408 7409 /** 7410 * Returns the offset of this time zone from UTC at the given date/time expressed in 7411 * milliseconds. If daylight saving 7412 * time is in effect at the given date/time, this method will return the offset value 7413 * adjusted by the amount of daylight saving. Negative numbers indicate offsets west 7414 * of UTC and conversely, positive numbers indicate offset east of UTC. 7415 * 7416 * @param {IDate=} date the date for which the offset is needed, or null for the 7417 * present date 7418 * @return {number} the number of milliseconds of offset from UTC that the given date is 7419 */ 7420 TimeZone.prototype.getOffsetMillis = function (date) { 7421 var ret; 7422 7423 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 7424 // well if we are in the overlap time at the end of DST 7425 if (this.isLocal && typeof(date.dst) === 'undefined') { 7426 var d = (!date) ? new Date() : new Date(date.getTimeExtended()); 7427 return -d.getTimezoneOffset() * 60000; 7428 } 7429 7430 ret = this.offset; 7431 7432 if (date && this.inDaylightTime(date)) { 7433 ret += this.dstSavings; 7434 } 7435 7436 return ret * 60000; 7437 }; 7438 7439 /** 7440 * Return the offset in milliseconds when the date has an RD number in wall 7441 * time rather than in UTC time. 7442 * @protected 7443 * @param date the date to check in wall time 7444 * @returns {number} the number of milliseconds of offset from UTC that the given date is 7445 */ 7446 TimeZone.prototype._getOffsetMillisWallTime = function (date) { 7447 var ret; 7448 7449 ret = this.offset; 7450 7451 if (date && this.inDaylightTime(date, true)) { 7452 ret += this.dstSavings; 7453 } 7454 7455 return ret * 60000; 7456 }; 7457 7458 /** 7459 * Returns the offset of this time zone from UTC at the given date/time. If daylight saving 7460 * time is in effect at the given date/time, this method will return the offset value 7461 * adjusted by the amount of daylight saving. 7462 * @param {IDate=} date the date for which the offset is needed 7463 * @return {string} the offset for the zone at the given date/time as a string in the 7464 * format "h:m:s" 7465 */ 7466 TimeZone.prototype.getOffsetStr = function (date) { 7467 var offset = this.getOffset(date), 7468 ret; 7469 7470 ret = offset.h; 7471 if (typeof(offset.m) !== 'undefined') { 7472 ret += ":" + offset.m; 7473 if (typeof(offset.s) !== 'undefined') { 7474 ret += ":" + offset.s; 7475 } 7476 } else { 7477 ret += ":0"; 7478 } 7479 7480 return ret; 7481 }; 7482 7483 /** 7484 * Gets the offset from UTC for this time zone. 7485 * @return {Object.<{h:number,m:number,s:number}>} an object giving the offset from 7486 * UTC for this time zone, in hours, minutes, and seconds 7487 */ 7488 TimeZone.prototype.getRawOffset = function () { 7489 var hours = MathUtils.down(this.offset/60), 7490 minutes = Math.abs(this.offset) - Math.abs(hours)*60; 7491 7492 var ret = { 7493 h: hours 7494 }; 7495 if (minutes != 0) { 7496 ret.m = minutes; 7497 } 7498 return ret; 7499 }; 7500 7501 /** 7502 * Gets the offset from UTC for this time zone expressed in milliseconds. Negative numbers 7503 * indicate zones west of UTC, and positive numbers indicate zones east of UTC. 7504 * 7505 * @return {number} an number giving the offset from 7506 * UTC for this time zone in milliseconds 7507 */ 7508 TimeZone.prototype.getRawOffsetMillis = function () { 7509 return this.offset * 60000; 7510 }; 7511 7512 /** 7513 * Gets the offset from UTC for this time zone without DST savings. 7514 * @return {string} the offset from UTC for this time zone, in the format "h:m:s" 7515 */ 7516 TimeZone.prototype.getRawOffsetStr = function () { 7517 var off = this.getRawOffset(); 7518 return off.h + ":" + (off.m || "0"); 7519 }; 7520 7521 /** 7522 * Return the amount of time in hours:minutes that the clock is advanced during 7523 * daylight savings time. 7524 * @return {Object.<{h:number,m:number,s:number}>} the amount of time that the 7525 * clock advances for DST in hours, minutes, and seconds 7526 */ 7527 TimeZone.prototype.getDSTSavings = function () { 7528 if (this.isLocal) { 7529 // take the absolute because the difference in the offsets may be positive or 7530 // negative, depending on the hemisphere 7531 var savings = Math.abs(this.offsetJan1 - this.offsetJun1); 7532 var hours = MathUtils.down(savings/60), 7533 minutes = savings - hours*60; 7534 return { 7535 h: hours, 7536 m: minutes 7537 }; 7538 } else if (this.zone && this.zone.s) { 7539 return this._offsetStringToObj(this.zone.s.v); // this.zone.start.savings 7540 } 7541 return {h:0}; 7542 }; 7543 7544 /** 7545 * Return the amount of time in hours:minutes that the clock is advanced during 7546 * daylight savings time. 7547 * @return {string} the amount of time that the clock advances for DST in the 7548 * format "h:m:s" 7549 */ 7550 TimeZone.prototype.getDSTSavingsStr = function () { 7551 if (this.isLocal) { 7552 var savings = this.getDSTSavings(); 7553 return savings.h + ":" + savings.m; 7554 } else if (typeof(this.offset) !== 'undefined' && this.zone && this.zone.s) { 7555 return this.zone.s.v; // this.zone.start.savings 7556 } 7557 return "0:0"; 7558 }; 7559 7560 /** 7561 * return the rd of the start of DST transition for the given year 7562 * @protected 7563 * @param {Object} rule set of rules 7564 * @param {number} year year to check 7565 * @return {number} the rd of the start of DST for the year 7566 */ 7567 TimeZone.prototype._calcRuleStart = function (rule, year) { 7568 var type = "=", 7569 weekday = 0, 7570 day, 7571 refDay, 7572 cal, 7573 hour = 0, 7574 minute = 0, 7575 second = 0, 7576 time, 7577 i; 7578 7579 if (typeof(rule.j) !== 'undefined') { 7580 refDay = new GregRataDie({ 7581 julianday: rule.j 7582 }); 7583 } else { 7584 if (rule.r.charAt(0) == 'l' || rule.r.charAt(0) == 'f') { 7585 cal = CalendarFactory({type: "gregorian"}); // can be synchronous 7586 type = rule.r.charAt(0); 7587 weekday = parseInt(rule.r.substring(1), 10); 7588 day = (type === 'l') ? cal.getMonLength(rule.m, year) : 1; 7589 //console.log("_calcRuleStart: Calculating the " + 7590 // (rule.r.charAt(0) == 'f' ? "first " : "last ") + weekday + 7591 // " of month " + rule.m); 7592 } else { 7593 i = rule.r.indexOf('<'); 7594 if (i == -1) { 7595 i = rule.r.indexOf('>'); 7596 } 7597 7598 if (i != -1) { 7599 type = rule.r.charAt(i); 7600 weekday = parseInt(rule.r.substring(0, i), 10); 7601 day = parseInt(rule.r.substring(i+1), 10); 7602 //console.log("_calcRuleStart: Calculating the " + weekday + 7603 // type + day + " of month " + rule.m); 7604 } else { 7605 day = parseInt(rule.r, 10); 7606 //console.log("_calcRuleStart: Calculating the " + day + " of month " + rule.m); 7607 } 7608 } 7609 7610 if (rule.t) { 7611 time = rule.t.split(":"); 7612 hour = parseInt(time[0], 10); 7613 if (time.length > 1) { 7614 minute = parseInt(time[1], 10); 7615 if (time.length > 2) { 7616 second = parseInt(time[2], 10); 7617 } 7618 } 7619 } 7620 //console.log("calculating rd of " + year + "/" + rule.m + "/" + day); 7621 refDay = new GregRataDie({ 7622 year: year, 7623 month: rule.m, 7624 day: day, 7625 hour: hour, 7626 minute: minute, 7627 second: second 7628 }); 7629 } 7630 //console.log("refDay is " + JSON.stringify(refDay)); 7631 var d = refDay.getRataDie(); 7632 7633 switch (type) { 7634 case 'l': 7635 case '<': 7636 //console.log("returning " + refDay.onOrBefore(rd, weekday)); 7637 d = refDay.onOrBefore(weekday); 7638 break; 7639 case 'f': 7640 case '>': 7641 //console.log("returning " + refDay.onOrAfterRd(rd, weekday)); 7642 d = refDay.onOrAfter(weekday); 7643 break; 7644 } 7645 return d; 7646 }; 7647 7648 /** 7649 * @private 7650 */ 7651 TimeZone.prototype._calcDSTSavings = function () { 7652 var saveParts = this.getDSTSavings(); 7653 7654 /** 7655 * @private 7656 * @type {number} savings in minutes when DST is in effect 7657 */ 7658 this.dstSavings = (Math.abs(saveParts.h || 0) * 60 + (saveParts.m || 0)) * MathUtils.signum(saveParts.h || 0); 7659 }; 7660 7661 /** 7662 * @private 7663 */ 7664 TimeZone.prototype._getDSTStartRule = function (year) { 7665 // TODO: update this when historic/future zones are supported 7666 return this.zone.s; 7667 }; 7668 7669 /** 7670 * @private 7671 */ 7672 TimeZone.prototype._getDSTEndRule = function (year) { 7673 // TODO: update this when historic/future zones are supported 7674 return this.zone.e; 7675 }; 7676 7677 /** 7678 * Returns whether or not the given date is in daylight saving time for the current 7679 * zone. Note that daylight savings time is observed for the summer. Because 7680 * the seasons are reversed, daylight savings time in the southern hemisphere usually 7681 * runs from the end of the year through New Years into the first few months of the 7682 * next year. This method will correctly calculate the start and end of DST for any 7683 * location. 7684 * 7685 * @param {IDate=} date a date for which the info about daylight time is being sought, 7686 * or undefined to tell whether we are currently in daylight savings time 7687 * @param {boolean=} wallTime if true, then the given date is in wall time. If false or 7688 * undefined, it is in the usual UTC time. 7689 * @return {boolean} true if the given date is in DST for the current zone, and false 7690 * otherwise. 7691 */ 7692 TimeZone.prototype.inDaylightTime = function (date, wallTime) { 7693 var rd, startRd, endRd, year; 7694 7695 if (this.isLocal) { 7696 // check if the dst property is defined -- the intrinsic JS Date object doesn't work so 7697 // well if we are in the overlap time at the end of DST, so we have to work around that 7698 // problem by adding in the savings ourselves 7699 var offset = this.offset * 60000; 7700 if (typeof(date.dst) !== 'undefined' && !date.dst) { 7701 offset += this.dstSavings * 60000; 7702 } 7703 7704 var d = new Date(date ? date.getTimeExtended() - offset: undefined); 7705 // the DST offset is always the one that is closest to positive infinity, no matter 7706 // if you are in the northern or southern hemisphere, east or west 7707 var dst = Math.max(this.offsetJan1, this.offsetJun1); 7708 return (-d.getTimezoneOffset() === dst); 7709 } 7710 7711 if (!date || !date.cal || date.cal.type !== "gregorian") { 7712 // convert to Gregorian so that we can tell if it is in DST or not 7713 var time = date && typeof(date.getTimeExtended) === 'function' ? date.getTimeExtended() : undefined; 7714 rd = new GregRataDie({unixtime: time}).getRataDie(); 7715 year = new Date(time).getUTCFullYear(); 7716 } else { 7717 rd = date.rd.getRataDie(); 7718 year = date.year; 7719 } 7720 // rd should be a Gregorian RD number now, in UTC 7721 7722 // if we aren't using daylight time in this zone for the given year, then we are 7723 // not in daylight time 7724 if (!this.useDaylightTime(year)) { 7725 return false; 7726 } 7727 7728 // these calculate the start/end in local wall time 7729 var startrule = this._getDSTStartRule(year); 7730 var endrule = this._getDSTEndRule(year); 7731 startRd = this._calcRuleStart(startrule, year); 7732 endRd = this._calcRuleStart(endrule, year); 7733 7734 if (wallTime) { 7735 // rd is in wall time, so we have to make sure to skip the missing time 7736 // at the start of DST when standard time ends and daylight time begins 7737 startRd += this.dstSavings/1440; 7738 } else { 7739 // rd is in UTC, so we have to convert the start/end to UTC time so 7740 // that they can be compared directly to the UTC rd number of the date 7741 7742 // when DST starts, time is standard time already, so we only have 7743 // to subtract the offset to get to UTC and not worry about the DST savings 7744 startRd -= this.offset/1440; 7745 7746 // when DST ends, time is in daylight time already, so we have to 7747 // subtract the DST savings to get back to standard time, then the 7748 // offset to get to UTC 7749 endRd -= (this.offset + this.dstSavings)/1440; 7750 } 7751 7752 // In the northern hemisphere, the start comes first some time in spring (Feb-Apr), 7753 // then the end some time in the fall (Sept-Nov). In the southern 7754 // hemisphere, it is the other way around because the seasons are reversed. Standard 7755 // time is still in the winter, but the winter months are May-Aug, and daylight 7756 // savings time usually starts Aug-Oct of one year and runs through Mar-May of the 7757 // next year. 7758 if (rd < endRd && endRd - rd <= this.dstSavings/1440 && typeof(date.dst) === 'boolean') { 7759 // take care of the magic overlap time at the end of DST 7760 return date.dst; 7761 } 7762 if (startRd < endRd) { 7763 // northern hemisphere 7764 return (rd >= startRd && rd < endRd) ? true : false; 7765 } 7766 // southern hemisphere 7767 return (rd >= startRd || rd < endRd) ? true : false; 7768 }; 7769 7770 /** 7771 * Returns true if this time zone switches to daylight savings time at some point 7772 * in the year, and false otherwise. 7773 * @param {number} year Whether or not the time zone uses daylight time in the given year. If 7774 * this parameter is not given, the current year is assumed. 7775 * @return {boolean} true if the time zone uses daylight savings time 7776 */ 7777 TimeZone.prototype.useDaylightTime = function (year) { 7778 7779 // this zone uses daylight savings time iff there is a rule defining when to start 7780 // and when to stop the DST 7781 return (this.isLocal && this.offsetJan1 !== this.offsetJun1) || 7782 (typeof(this.zone) !== 'undefined' && 7783 typeof(this.zone.s) !== 'undefined' && 7784 typeof(this.zone.e) !== 'undefined'); 7785 }; 7786 7787 /** 7788 * Returns the ISO 3166 code of the country for which this time zone is defined. 7789 * @return {string} the ISO 3166 code of the country for this zone 7790 */ 7791 TimeZone.prototype.getCountry = function () { 7792 return this.zone.c; 7793 }; 7794 7795 7796 7797 /*< ISet.js */ 7798 /* 7799 * ISet.js - ilib Set class definition for platforms older than ES6 7800 * 7801 * Copyright © 2015, JEDLSoft 7802 * 7803 * Licensed under the Apache License, Version 2.0 (the "License"); 7804 * you may not use this file except in compliance with the License. 7805 * You may obtain a copy of the License at 7806 * 7807 * http://www.apache.org/licenses/LICENSE-2.0 7808 * 7809 * Unless required by applicable law or agreed to in writing, software 7810 * distributed under the License is distributed on an "AS IS" BASIS, 7811 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7812 * 7813 * See the License for the specific language governing permissions and 7814 * limitations under the License. 7815 */ 7816 7817 /** 7818 * Create a new set with elements in the given array. The type of 7819 * the set is gleaned from the type of the first element in the 7820 * elements array, or the first element added to the set. The type 7821 * may be "string" or "number", and all elements will be returned 7822 * as elements of that type. 7823 * 7824 * @class 7825 * @param {Array.<string|number>=} elements initial elements to add to the set 7826 * @constructor 7827 */ 7828 var ISet = function(elements) { 7829 this.elements = {}; 7830 7831 if (elements && elements.length) { 7832 for (var i = 0; i < elements.length; i++) { 7833 this.elements[elements[i]] = true; 7834 } 7835 7836 this.type = typeof(elements[0]); 7837 } 7838 }; 7839 7840 /** 7841 * @private 7842 */ 7843 ISet.prototype._addOne = function(element) { 7844 if (this.isEmpty()) { 7845 this.type = typeof(element); 7846 } 7847 7848 if (!this.elements[element]) { 7849 this.elements[element] = true; 7850 return true; 7851 } 7852 7853 return false; 7854 }; 7855 7856 /** 7857 * Adds the specified element or array of elements to this set if it is or they are not 7858 * already present. 7859 * 7860 * @param {*|Array.<*>} element element or array of elements to add 7861 * @return {boolean} true if this set did not already contain the specified element[s] 7862 */ 7863 ISet.prototype.add = function(element) { 7864 var ret = false; 7865 7866 if (typeof(element) === "object") { 7867 for (var i = 0; i < element.length; i++) { 7868 ret = this._addOne(element[i]) || ret; 7869 } 7870 } else { 7871 ret = this._addOne(element); 7872 } 7873 7874 return ret; 7875 }; 7876 7877 /** 7878 * Removes all of the elements from this set. 7879 */ 7880 ISet.prototype.clear = function() { 7881 this.elements = {}; 7882 }; 7883 7884 /** 7885 * Returns true if this set contains the specified element. 7886 * @param {*} element the element to test 7887 * @return {boolean} 7888 */ 7889 ISet.prototype.contains = function(element) { 7890 return this.elements[element] || false; 7891 }; 7892 7893 /** 7894 * Returns true if this set contains no elements. 7895 * @return {boolean} 7896 */ 7897 ISet.prototype.isEmpty = function() { 7898 return (Object.keys(this.elements).length === 0); 7899 }; 7900 7901 /** 7902 * Removes the specified element from this set if it is present. 7903 * @param {*} element the element to remove 7904 * @return {boolean} true if the set contained the specified element 7905 */ 7906 ISet.prototype.remove = function(element) { 7907 if (this.elements[element]) { 7908 delete this.elements[element]; 7909 return true; 7910 } 7911 7912 return false; 7913 }; 7914 7915 /** 7916 * Return the set as a javascript array. 7917 * @return {Array.<*>} the set represented as a javascript array 7918 */ 7919 ISet.prototype.asArray = function() { 7920 var keys = Object.keys(this.elements); 7921 7922 // keys is an array of strings. Convert to numbers if necessary 7923 if (this.type === "number") { 7924 var tmp = []; 7925 for (var i = 0; i < keys.length; i++) { 7926 tmp.push(Number(keys[i]).valueOf()); 7927 } 7928 keys = tmp; 7929 } 7930 7931 return keys; 7932 }; 7933 7934 /** 7935 * Represents the current set as json. 7936 * @return {string} the current set represented as json 7937 */ 7938 ISet.prototype.toJson = function() { 7939 return JSON.stringify(this.asArray()); 7940 }; 7941 7942 /** 7943 * Convert to a javascript representation of this object. 7944 * In this case, it is a normal JS array. 7945 * @return {*} the JS representation of this object 7946 */ 7947 ISet.prototype.toJS = function() { 7948 return this.asArray(); 7949 }; 7950 7951 /** 7952 * Convert from a js representation to an internal one. 7953 * @return {ISet|undefined} the current object, or undefined if the conversion did not work 7954 */ 7955 ISet.prototype.fromJS = function(obj) { 7956 return this.add(obj) ? this : undefined; 7957 }; 7958 7959 7960 7961 /*< DateFmt.js */ 7962 /* 7963 * DateFmt.js - Date formatter definition 7964 * 7965 * Copyright © 2012-2015, 2018, JEDLSoft 7966 * 7967 * Licensed under the Apache License, Version 2.0 (the "License"); 7968 * you may not use this file except in compliance with the License. 7969 * You may obtain a copy of the License at 7970 * 7971 * http://www.apache.org/licenses/LICENSE-2.0 7972 * 7973 * Unless required by applicable law or agreed to in writing, software 7974 * distributed under the License is distributed on an "AS IS" BASIS, 7975 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 7976 * 7977 * See the License for the specific language governing permissions and 7978 * limitations under the License. 7979 */ 7980 7981 /* 7982 !depends 7983 ilib.js 7984 Locale.js 7985 IDate.js 7986 DateFactory.js 7987 ResBundle.js 7988 CalendarFactory.js 7989 LocaleInfo.js 7990 TimeZone.js 7991 GregorianCal.js 7992 JSUtils.js 7993 Utils.js 7994 ISet.js 7995 */ 7996 7997 // !data dateformats sysres 7998 7999 8000 8001 8002 8003 8004 /** 8005 * @class 8006 * Create a new date formatter instance. The date formatter is immutable once 8007 * it is created, but can format as many different dates as needed with the same 8008 * options. Create different date formatter instances for different purposes 8009 * and then keep them cached for use later if you have more than one date to 8010 * format.<p> 8011 * 8012 * The options may contain any of the following properties: 8013 * 8014 * <ul> 8015 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8016 * not specified, then the default locale of the app or web page will be used. 8017 * 8018 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 8019 * be a sting containing the name of the calendar. Currently, the supported 8020 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 8021 * calendar is not specified, then the default calendar for the locale is used. When the 8022 * calendar type is specified, then the format method must be called with an instance of 8023 * the appropriate date type. (eg. Gregorian calendar means that the format method must 8024 * be called with a GregDate instance.) 8025 * 8026 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 8027 * instance or a time zone specifier from the IANA list of time zone database names 8028 * (eg. "America/Los_Angeles"), 8029 * the string "local", or a string specifying the offset in RFC 822 format. The IANA 8030 * list of time zone names can be viewed at 8031 * <a href="http://en.wikipedia.org/wiki/List_of_tz_database_time_zones">this page</a>. 8032 * If the time zone is given as "local", the offset from UTC as given by 8033 * the Javascript system is used. If the offset is given as an RFC 822 style offset 8034 * specifier, it will parse that string and use the resulting offset. If the time zone 8035 * is not specified, the 8036 * default time zone for the locale is used. If both the date object and this formatter 8037 * instance contain time zones and those time zones are different from each other, the 8038 * formatter will calculate the offset between the time zones and subtract it from the 8039 * date before formatting the result for the current time zone. The theory is that a date 8040 * object that contains a time zone specifies a specific instant in time that is valid 8041 * around the world, whereas a date object without one is a local time and can only be 8042 * used for doing things in the local time zone of the user. 8043 * 8044 * <li><i>type</i> - Specify whether this formatter should format times only, dates only, or 8045 * both times and dates together. Valid values are "time", "date", and "datetime". Note that 8046 * in some locales, the standard format uses the order "time followed by date" and in others, 8047 * the order is exactly opposite, so it is better to create a single "datetime" formatter 8048 * than it is to create a time formatter and a date formatter separately and concatenate the 8049 * results. A "datetime" formatter will get the order correct for the locale.<p> 8050 * 8051 * The default type if none is specified in with the type option is "date". 8052 * 8053 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 8054 * formatted string. 8055 * 8056 * <ul> 8057 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 8058 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 8059 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 8060 * components may still be abbreviated 8061 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 8062 * components are spelled out completely 8063 * </ul> 8064 * 8065 * eg. The "short" format for an en_US date may be "MM/dd/yy", whereas the long format might be "d MMM, yyyy". In the long 8066 * format, the month name is textual instead of numeric and is longer, the year is 4 digits instead of 2, and the format 8067 * contains slightly more spaces and formatting characters.<p> 8068 * 8069 * Note that the length parameter does not specify which components are to be formatted. Use the "date" and the "time" 8070 * properties to specify the components. Also, very few of the components of a time format differ according to the length, 8071 * so this property has little to no affect on time formatting. 8072 * 8073 * <li><i>date</i> - This property tells 8074 * which components of a date format to use. For example, 8075 * sometimes you may wish to format a date that only contains the month and date 8076 * without the year, such as when displaying a person's yearly birthday. The value 8077 * of this property allows you to specify only those components you want to see in the 8078 * final output, ordered correctly for the locale. <p> 8079 * 8080 * Valid values are: 8081 * 8082 * <ul> 8083 * <li><i>dmwy</i> - format all components, weekday, date, month, and year 8084 * <li><i>dmy</i> - format only date, month, and year 8085 * <li><i>dmw</i> - format only weekday, date, and month 8086 * <li><i>dm</i> - format only date and month 8087 * <li><i>my</i> - format only month and year 8088 * <li><i>dw</i> - format only the weekday and date 8089 * <li><i>d</i> - format only the date 8090 * <li><i>m</i> - format only the month, in numbers for shorter lengths, and letters for 8091 * longer lengths 8092 * <li><i>n</i> - format only the month, in letters only for all lengths 8093 * <li><i>y</i> - format only the year 8094 * </ul> 8095 * Default components, if this property is not specified, is "dmy". This property may be specified 8096 * but has no affect if the current formatter is for times only.<p> 8097 * 8098 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 8099 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 8100 * It will not extract the length from the skeleton so you still need to pass the length property, 8101 * but it will extract the date components. 8102 * 8103 * <li><i>time</i> - This property gives which components of a time format to use. The time will be formatted 8104 * correctly for the locale with only the time components requested. For example, a clock might only display 8105 * the hour and minute and not need the seconds or the am/pm component. In this case, the time property should be set 8106 * to "hm". <p> 8107 * 8108 * Valid values for this property are: 8109 * 8110 * <ul> 8111 * <li><i>ahmsz</i> - format the hours, minutes, seconds, am/pm (if using a 12 hour clock), and the time zone 8112 * <li><i>ahms</i> - format the hours, minutes, seconds, and am/pm (if using a 12 hour clock) 8113 * <li><i>hmsz</i> - format the hours, minutes, seconds, and the time zone 8114 * <li><i>hms</i> - format the hours, minutes, and seconds 8115 * <li><i>ahmz</i> - format the hours, minutes, am/pm (if using a 12 hour clock), and the time zone 8116 * <li><i>ahm</i> - format the hours, minutes, and am/pm (if using a 12 hour clock) 8117 * <li><i>hmz</i> - format the hours, minutes, and the time zone 8118 * <li><i>ah</i> - format only the hours and am/pm if using a 12 hour clock 8119 * <li><i>hm</i> - format only the hours and minutes 8120 * <li><i>ms</i> - format only the minutes and seconds 8121 * <li><i>h</i> - format only the hours 8122 * <li><i>m</i> - format only the minutes 8123 * <li><i>s</i> - format only the seconds 8124 * </ul> 8125 * 8126 * If you want to format a length of time instead of a particular instant 8127 * in time, use the duration formatter object (DurationFmt) instead because this 8128 * formatter is geared towards instants. A date formatter will make sure that each component of the 8129 * time is within the normal range 8130 * for that component. That is, the minutes will always be between 0 and 59, no matter 8131 * what is specified in the date to format. A duration format will allow the number 8132 * of minutes to exceed 59 if, for example, you were displaying the length of 8133 * a movie of 198 minutes.<p> 8134 * 8135 * Default value if this property is not specified is "hma".<p> 8136 * 8137 * As of ilib 12.0, you can now pass ICU style skeletons in this option similar to the ones you 8138 * get from <a href="http://icu-project.org/apiref/icu4c432/classDateTimePatternGenerator.html#aa30c251609c1eea5ad60c95fc497251e">DateTimePatternGenerator.getSkeleton()</a>. 8139 * It will not extract the length from the skeleton so you still need to pass the length property, 8140 * but it will extract the time components. 8141 * 8142 * <li><i>clock</i> - specify that the time formatter should use a 12 or 24 hour clock. 8143 * Valid values are "12" and "24".<p> 8144 * 8145 * In some locales, both clocks are used. For example, in en_US, the general populace uses 8146 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 8147 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 8148 * construct a formatter that overrides the default for the locale.<p> 8149 * 8150 * If this property is not specified, the default is to use the most widely used convention 8151 * for the locale. 8152 * 8153 * <li><i>template</i> - use the given template string as a fixed format when formatting 8154 * the date/time. Valid codes to use in a template string are as follows: 8155 * 8156 * <ul> 8157 * <li><i>a</i> - am/pm marker 8158 * <li><i>d</i> - 1 or 2 digit date of month, not padded 8159 * <li><i>dd</i> - 1 or 2 digit date of month, 0 padded to 2 digits 8160 * <li><i>O</i> - ordinal representation of the date of month (eg. "1st", "2nd", etc.) 8161 * <li><i>D</i> - 1 to 3 digit day of year 8162 * <li><i>DD</i> - 1 to 3 digit day of year, 0 padded to 2 digits 8163 * <li><i>DDD</i> - 1 to 3 digit day of year, 0 padded to 3 digits 8164 * <li><i>M</i> - 1 or 2 digit month number, not padded 8165 * <li><i>MM</i> - 1 or 2 digit month number, 0 padded to 2 digits 8166 * <li><i>N</i> - 1 character month name abbreviation 8167 * <li><i>NN</i> - 2 character month name abbreviation 8168 * <li><i>MMM</i> - 3 character month month name abbreviation 8169 * <li><i>MMMM</i> - fully spelled out month name 8170 * <li><i>yy</i> - 2 digit year 8171 * <li><i>yyyy</i> - 4 digit year 8172 * <li><i>E</i> - day-of-week name, abbreviated to a single character 8173 * <li><i>EE</i> - day-of-week name, abbreviated to a max of 2 characters 8174 * <li><i>EEE</i> - day-of-week name, abbreviated to a max of 3 characters 8175 * <li><i>EEEE</i> - day-of-week name fully spelled out 8176 * <li><i>G</i> - era designator 8177 * <li><i>w</i> - week number in year 8178 * <li><i>ww</i> - week number in year, 0 padded to 2 digits 8179 * <li><i>W</i> - week in month 8180 * <li><i>h</i> - hour (12 followed by 1 to 11) 8181 * <li><i>hh</i> - hour (12, followed by 1 to 11), 0 padded to 2 digits 8182 * <li><i>k</i> - hour (1 to 24) 8183 * <li><i>kk</i> - hour (1 to 24), 0 padded to 2 digits 8184 * <li><i>H</i> - hour (0 to 23) 8185 * <li><i>HH</i> - hour (0 to 23), 0 padded to 2 digits 8186 * <li><i>K</i> - hour (0 to 11) 8187 * <li><i>KK</i> - hour (0 to 11), 0 padded to 2 digits 8188 * <li><i>m</i> - minute in hour 8189 * <li><i>mm</i> - minute in hour, 0 padded to 2 digits 8190 * <li><i>s</i> - second in minute 8191 * <li><i>ss</i> - second in minute, 0 padded to 2 digits 8192 * <li><i>S</i> - millisecond (1 to 3 digits) 8193 * <li><i>SSS</i> - millisecond, 0 padded to 3 digits 8194 * <li><i>z</i> - general time zone 8195 * <li><i>Z</i> - RFC 822 time zone 8196 * </ul> 8197 * 8198 * <li><i>useNative</i> - the flag used to determine whether to use the native script settings 8199 * for formatting the numbers. 8200 * 8201 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8202 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8203 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8204 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8205 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8206 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8207 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8208 * when formatting dates in the Gregorian calendar. 8209 * 8210 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 8211 * loaded. When the onLoad option is given, the DateFmt object will attempt to 8212 * load any missing locale data using the ilib loader callback. 8213 * When the constructor is done (even if the data is already preassembled), the 8214 * onLoad function is called with the current instance as a parameter, so this 8215 * callback can be used with preassembled or dynamic loading or a mix of the two. 8216 * 8217 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 8218 * asynchronously. If this option is given as "false", then the "onLoad" 8219 * callback must be given, as the instance returned from this constructor will 8220 * not be usable for a while. 8221 * 8222 * <li><i>loadParams</i> - an object containing parameters to pass to the 8223 * loader callback function when locale data is missing. The parameters are not 8224 * interpretted or modified in any way. They are simply passed along. The object 8225 * may contain any property/value pairs as long as the calling code is in 8226 * agreement with the loader callback function as to what those parameters mean. 8227 * </ul> 8228 * 8229 * Any substring containing letters within single or double quotes will be used 8230 * as-is in the final output and will not be interpretted for codes as above.<p> 8231 * 8232 * Example: a date format in Spanish might be given as: "'El' d. 'de' MMMM", where 8233 * the 'El' and the 'de' are left as-is in the output because they are quoted. Typical 8234 * output for this example template might be, "El 5. de Mayo". 8235 * 8236 * The following options will be used when formatting a date/time with an explicit 8237 * template: 8238 * 8239 * <ul> 8240 * <li>locale - the locale is only used for 8241 * translations of things like month names or day-of-week names. 8242 * <li>calendar - used to translate a date instance into date/time component values 8243 * that can be formatted into the template 8244 * <li>timezone - used to figure out the offset to add or subtract from the time to 8245 * get the final time component values 8246 * <li>clock - used to figure out whether to format times with a 12 or 24 hour clock. 8247 * If this option is specified, it will override the hours portion of a time format. 8248 * That is, "hh" is switched with "HH" and "kk" is switched with "KK" as appropriate. 8249 * If this option is not specified, the 12/24 code in the template will dictate whether 8250 * to use the 12 or 24 clock, and the 12/24 default in the locale will be ignored. 8251 * </ul> 8252 * 8253 * All other options will be ignored and their corresponding getter methods will 8254 * return the empty string.<p> 8255 * 8256 * 8257 * @constructor 8258 * @param {Object} options options governing the way this date formatter instance works 8259 */ 8260 var DateFmt = function(options) { 8261 var arr, i, bad, c, comps, 8262 sync = true, 8263 loadParams = undefined; 8264 8265 this.locale = new Locale(); 8266 this.type = "date"; 8267 this.length = "s"; 8268 this.dateComponents = "dmy"; 8269 this.timeComponents = "ahm"; 8270 this.meridiems = "default"; 8271 8272 options = options || {sync: true}; 8273 if (options.locale) { 8274 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 8275 } 8276 8277 if (options.type) { 8278 if (options.type === 'date' || options.type === 'time' || options.type === 'datetime') { 8279 this.type = options.type; 8280 } 8281 } 8282 8283 if (options.calendar) { 8284 this.calName = options.calendar; 8285 } 8286 8287 if (options.length) { 8288 if (options.length === 'short' || 8289 options.length === 'medium' || 8290 options.length === 'long' || 8291 options.length === 'full') { 8292 // only use the first char to save space in the json files 8293 this.length = options.length.charAt(0); 8294 } 8295 } 8296 8297 if (options.date) { 8298 arr = options.date.split(""); 8299 var dateComps = new ISet(); 8300 bad = false; 8301 for (i = 0; i < arr.length; i++) { 8302 c = arr[i].toLowerCase(); 8303 if (c === "e") c = "w"; // map ICU -> ilib 8304 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'n') { 8305 // ignore time components and the era 8306 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z' && c !== 'g') { 8307 bad = true; 8308 break; 8309 } 8310 } else { 8311 dateComps.add(c); 8312 } 8313 } 8314 if (!bad) { 8315 comps = dateComps.asArray().sort(function (left, right) { 8316 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8317 }); 8318 this.dateComponents = comps.join(""); 8319 } 8320 } 8321 8322 if (options.time) { 8323 arr = options.time.split(""); 8324 var timeComps = new ISet(); 8325 this.badTime = false; 8326 for (i = 0; i < arr.length; i++) { 8327 c = arr[i].toLowerCase(); 8328 if (c !== 'h' && c !== 'm' && c !== 's' && c !== 'a' && c !== 'z') { 8329 // ignore the date components 8330 if (c !== 'd' && c !== 'm' && c !== 'y' && c !== 'w' && c !== 'e' && c !== 'n' && c !== 'g') { 8331 this.badTime = true; 8332 break; 8333 } 8334 } else { 8335 timeComps.add(c); 8336 } 8337 } 8338 if (!this.badTime) { 8339 comps = timeComps.asArray().sort(function (left, right) { 8340 return (left < right) ? -1 : ((right < left) ? 1 : 0); 8341 }); 8342 this.timeComponents = comps.join(""); 8343 } 8344 } 8345 8346 if (options.clock && (options.clock === '12' || options.clock === '24')) { 8347 this.clock = options.clock; 8348 } 8349 8350 if (options.template) { 8351 // many options are not useful when specifying the template directly, so zero 8352 // them out. 8353 this.type = ""; 8354 this.length = ""; 8355 this.dateComponents = ""; 8356 this.timeComponents = ""; 8357 8358 this.template = options.template; 8359 } 8360 8361 if (options.timezone) { 8362 if (options.timezone instanceof TimeZone) { 8363 this.tz = options.timezone; 8364 this.timezone = this.tz.getId(); 8365 } else { 8366 this.timezone = options.timezone; 8367 } 8368 } 8369 8370 if (typeof(options.useNative) === 'boolean') { 8371 this.useNative = options.useNative; 8372 } 8373 8374 if (typeof(options.meridiems) !== 'undefined' && 8375 (options.meridiems === "chinese" || 8376 options.meridiems === "gregorian" || 8377 options.meridiems === "ethiopic")) { 8378 this.meridiems = options.meridiems; 8379 } 8380 8381 if (typeof(options.sync) !== 'undefined') { 8382 sync = (options.sync === true); 8383 } 8384 8385 loadParams = options.loadParams; 8386 8387 new LocaleInfo(this.locale, { 8388 sync: sync, 8389 loadParams: loadParams, 8390 onLoad: ilib.bind(this, function (li) { 8391 this.locinfo = li; 8392 8393 // get the default calendar name from the locale, and if the locale doesn't define 8394 // one, use the hard-coded gregorian as the last resort 8395 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 8396 if (ilib.isDynCode()) { 8397 // If we are running in the dynamic code loading assembly of ilib, the following 8398 // will attempt to dynamically load the calendar date class for this calendar. If 8399 // it doesn't work, this just goes on and it will use Gregorian instead. 8400 DateFactory._dynLoadDate(this.calName); 8401 } 8402 8403 CalendarFactory({ 8404 type: this.calName, 8405 sync: sync, 8406 loadParams: loadParams, 8407 onLoad: ilib.bind(this, function(cal) { 8408 this.cal = cal; 8409 8410 if (!this.cal) { 8411 // can be synchronous 8412 this.cal = new GregorianCal(); 8413 } 8414 if (this.meridiems === "default") { 8415 this.meridiems = li.getMeridiemsStyle(); 8416 } 8417 8418 // load the strings used to translate the components 8419 new ResBundle({ 8420 locale: this.locale, 8421 name: "sysres", 8422 sync: sync, 8423 loadParams: loadParams, 8424 onLoad: ilib.bind(this, function (rb) { 8425 this.sysres = rb; 8426 8427 if (!this.tz) { 8428 var timezone = options.timezone; 8429 if (!timezone && !options.locale) { 8430 timezone = "local"; 8431 } 8432 8433 new TimeZone({ 8434 locale: this.locale, 8435 id: timezone, 8436 sync: sync, 8437 loadParams: loadParams, 8438 onLoad: ilib.bind(this, function(tz) { 8439 this.tz = tz; 8440 this._init(options); 8441 }) 8442 }); 8443 } else { 8444 this._init(options); 8445 } 8446 }) 8447 }); 8448 }) 8449 }); 8450 }) 8451 }); 8452 }; 8453 8454 // used in getLength 8455 DateFmt.lenmap = { 8456 "s": "short", 8457 "m": "medium", 8458 "l": "long", 8459 "f": "full" 8460 }; 8461 8462 DateFmt.defaultFmt = { 8463 "gregorian": { 8464 "order": "{date} {time}", 8465 "date": { 8466 "dmwy": "EEE d/MM/yyyy", 8467 "dmy": "d/MM/yyyy", 8468 "dmw": "EEE d/MM", 8469 "dm": "d/MM", 8470 "my": "MM/yyyy", 8471 "dw": "EEE d", 8472 "d": "dd", 8473 "m": "MM", 8474 "y": "yyyy", 8475 "n": "NN", 8476 "w": "EEE" 8477 }, 8478 "time": { 8479 "12": "h:mm:ssa", 8480 "24": "H:mm:ss" 8481 }, 8482 "range": { 8483 "c00": "{st} - {et}, {sd}/{sm}/{sy}", 8484 "c01": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8485 "c02": "{sd}/{sm} {st} - {ed}/{em} {et}, {sy}", 8486 "c03": "{sd}/{sm}/{sy} {st} - {ed}/{em}/{ey} {et}", 8487 "c10": "{sd}-{ed}/{sm}/{sy}", 8488 "c11": "{sd}/{sm} - {ed}/{em} {sy}", 8489 "c12": "{sd}/{sm}/{sy} - {ed}/{em}/{ey}", 8490 "c20": "{sm}/{sy} - {em}/{ey}", 8491 "c30": "{sy} - {ey}" 8492 } 8493 }, 8494 "islamic": "gregorian", 8495 "hebrew": "gregorian", 8496 "julian": "gregorian", 8497 "buddhist": "gregorian", 8498 "persian": "gregorian", 8499 "persian-algo": "gregorian", 8500 "han": "gregorian" 8501 }; 8502 8503 /** 8504 * @static 8505 * @private 8506 */ 8507 DateFmt.monthNameLenMap = { 8508 "short" : "N", 8509 "medium": "NN", 8510 "long": "MMM", 8511 "full": "MMMM" 8512 }; 8513 8514 /** 8515 * @static 8516 * @private 8517 */ 8518 DateFmt.weekDayLenMap = { 8519 "short" : "E", 8520 "medium": "EE", 8521 "long": "EEE", 8522 "full": "EEEE" 8523 }; 8524 8525 /** 8526 * Return the range of possible meridiems (times of day like "AM" or 8527 * "PM") in this date formatter.<p> 8528 * 8529 * The options may contain any of the following properties: 8530 * 8531 * <ul> 8532 * <li><i>locale</i> - locale to use when formatting the date/time. If the locale is 8533 * not specified, then the default locale of the app or web page will be used. 8534 * 8535 * <li><i>meridiems</i> - string that specifies what style of meridiems to use with this 8536 * format. The choices are "default", "gregorian", "ethiopic", and "chinese". The "default" 8537 * style is often the simple Gregorian AM/PM, but the actual style is chosen by the locale. 8538 * (For almost all locales, the Gregorian AM/PM style is most frequently used.) 8539 * The "ethiopic" style uses 5 different meridiems for "morning", "noon", "afternoon", 8540 * "evening", and "night". The "chinese" style uses 7 different meridiems corresponding 8541 * to the various parts of the day. N.B. Even for the Chinese locales, the default is "gregorian" 8542 * when formatting dates in the Gregorian calendar. 8543 * </ul> 8544 * 8545 * @static 8546 * @public 8547 * @param {Object} options options governing the way this date formatter instance works for getting meridiems range 8548 * @return {Array.<{name:string,start:string,end:string}>} 8549 */ 8550 DateFmt.getMeridiemsRange = function (options) { 8551 options = options || {sync: true}; 8552 var args = JSUtils.merge({}, options); 8553 args.onLoad = function(fmt) { 8554 if (typeof(options.onLoad) === "function") { 8555 options.onLoad(fmt.getMeridiemsRange()); 8556 } 8557 }; 8558 var fmt = new DateFmt(args); 8559 8560 return fmt.getMeridiemsRange(); 8561 }; 8562 8563 DateFmt.prototype = { 8564 /** 8565 * @private 8566 * Finish initializing the formatter object 8567 */ 8568 _init: function(options) { 8569 if (typeof (options.sync) === 'undefined') { 8570 options.sync = true; 8571 } 8572 if (!this.template) { 8573 Utils.loadData({ 8574 object: "DateFmt", 8575 locale: this.locale, 8576 name: "dateformats.json", 8577 sync: options.sync, 8578 loadParams: options.loadParams, 8579 callback: ilib.bind(this, function (formats) { 8580 var spec = this.locale.getSpec().replace(/-/g, '_'); 8581 if (!formats) { 8582 formats = ilib.data.dateformats || DateFmt.defaultFmt; 8583 ilib.data.cache.DateFmt[spec] = formats; 8584 } 8585 8586 if (typeof(this.clock) === 'undefined') { 8587 // default to the locale instead 8588 this.clock = this.locinfo.getClock(); 8589 } 8590 8591 var ret = this; 8592 8593 if (typeof(options.sync) === "boolean" && !options.sync) { 8594 // in async mode, capture the exception and call the callback with "undefined" 8595 try { 8596 this._initTemplate(formats); 8597 this._massageTemplate(); 8598 } catch (e) { 8599 ret = undefined; 8600 } 8601 } else { 8602 // in sync mode, allow the exception to percolate upwards 8603 this._initTemplate(formats); 8604 this._massageTemplate(); 8605 } 8606 8607 if (typeof(options.onLoad) === 'function') { 8608 options.onLoad(ret); 8609 } 8610 }) 8611 }); 8612 } else { 8613 this._massageTemplate(); 8614 8615 if (typeof(options.onLoad) === 'function') { 8616 options.onLoad(this); 8617 } 8618 } 8619 }, 8620 8621 /** 8622 * @protected 8623 * @param {string|{ 8624 * order:(string|{ 8625 * s:string, 8626 * m:string, 8627 * l:string, 8628 * f:string 8629 * }), 8630 * date:Object.<string, (string|{ 8631 * s:string, 8632 * m:string, 8633 * l:string, 8634 * f:string 8635 * })>, 8636 * time:Object.<string,Object.<string,(string|{ 8637 * s:string, 8638 * m:string, 8639 * l:string, 8640 * f:string 8641 * })>>, 8642 * range:Object.<string, (string|{ 8643 * s:string, 8644 * m:string, 8645 * l:string, 8646 * f:string 8647 * })> 8648 * }} formats 8649 */ 8650 _initTemplate: function (formats) { 8651 if (formats[this.calName]) { 8652 var name = formats[this.calName]; 8653 // may be an alias to another calendar type 8654 this.formats = (typeof(name) === "string") ? formats[name] : name; 8655 8656 this.template = ""; 8657 8658 switch (this.type) { 8659 case "datetime": 8660 this.template = (this.formats && this._getLengthFormat(this.formats.order, this.length)) || "{date} {time}"; 8661 this.template = this.template.replace("{date}", this._getFormat(this.formats.date, this.dateComponents, this.length) || ""); 8662 this.template = this.template.replace("{time}", this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length) || ""); 8663 break; 8664 case "date": 8665 this.template = this._getFormat(this.formats.date, this.dateComponents, this.length); 8666 break; 8667 case "time": 8668 this.template = this._getFormat(this.formats.time[this.clock], this.timeComponents, this.length); 8669 break; 8670 } 8671 8672 // calculate what order the components appear in for this locale 8673 this.componentOrder = this._getFormat(this.formats.date, "dmy", "l"). 8674 replace(/[^dMy]/g, ""). 8675 replace(/y+/, "y"). 8676 replace(/d+/, "d"). 8677 replace(/M+/, "m"); 8678 } else { 8679 throw "No formats available for calendar " + this.calName + " in locale " + this.locale.toString(); 8680 } 8681 }, 8682 8683 /** 8684 * @protected 8685 */ 8686 _massageTemplate: function () { 8687 var i; 8688 8689 if (this.clock && this.template) { 8690 // explicitly set the hours to the requested type 8691 var temp = ""; 8692 switch (this.clock) { 8693 case "24": 8694 for (i = 0; i < this.template.length; i++) { 8695 if (this.template.charAt(i) == "'") { 8696 temp += this.template.charAt(i++); 8697 while (i < this.template.length && this.template.charAt(i) !== "'") { 8698 temp += this.template.charAt(i++); 8699 } 8700 if (i < this.template.length) { 8701 temp += this.template.charAt(i); 8702 } 8703 } else if (this.template.charAt(i) == 'K') { 8704 temp += 'k'; 8705 } else if (this.template.charAt(i) == 'h') { 8706 temp += 'H'; 8707 } else { 8708 temp += this.template.charAt(i); 8709 } 8710 } 8711 this.template = temp; 8712 break; 8713 case "12": 8714 for (i = 0; i < this.template.length; i++) { 8715 if (this.template.charAt(i) == "'") { 8716 temp += this.template.charAt(i++); 8717 while (i < this.template.length && this.template.charAt(i) !== "'") { 8718 temp += this.template.charAt(i++); 8719 } 8720 if (i < this.template.length) { 8721 temp += this.template.charAt(i); 8722 } 8723 } else if (this.template.charAt(i) == 'k') { 8724 temp += 'K'; 8725 } else if (this.template.charAt(i) == 'H') { 8726 temp += 'h'; 8727 } else { 8728 temp += this.template.charAt(i); 8729 } 8730 } 8731 this.template = temp; 8732 break; 8733 } 8734 } 8735 8736 // tokenize it now for easy formatting 8737 this.templateArr = this._tokenize(this.template); 8738 8739 var digits; 8740 // set up the mapping to native or alternate digits if necessary 8741 if (typeof(this.useNative) === "boolean") { 8742 if (this.useNative) { 8743 digits = this.locinfo.getNativeDigits(); 8744 if (digits) { 8745 this.digits = digits; 8746 } 8747 } 8748 } else if (this.locinfo.getDigitsStyle() === "native") { 8749 digits = this.locinfo.getNativeDigits(); 8750 if (digits) { 8751 this.useNative = true; 8752 this.digits = digits; 8753 } 8754 } 8755 }, 8756 8757 /** 8758 * Convert the template into an array of date components separated by formatting chars. 8759 * @protected 8760 * @param {string} template Format template to tokenize into components 8761 * @return {Array.<string>} a tokenized array of date format components 8762 */ 8763 _tokenize: function (template) { 8764 var i = 0, start, ch, letter, arr = []; 8765 8766 // console.log("_tokenize: tokenizing template " + template); 8767 if (template) { 8768 while (i < template.length) { 8769 ch = template.charAt(i); 8770 start = i; 8771 if (ch === "'") { 8772 // console.log("found quoted string"); 8773 i++; 8774 // escaped string - push as-is, then dequote later 8775 while (i < template.length && template.charAt(i) !== "'") { 8776 i++; 8777 } 8778 if (i < template.length) { 8779 i++; // grab the other quote too 8780 } 8781 } else if ((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z')) { 8782 letter = template.charAt(i); 8783 // console.log("found letters " + letter); 8784 while (i < template.length && ch === letter) { 8785 ch = template.charAt(++i); 8786 } 8787 } else { 8788 // console.log("found other"); 8789 while (i < template.length && ch !== "'" && (ch < 'a' || ch > 'z') && (ch < 'A' || ch > 'Z')) { 8790 ch = template.charAt(++i); 8791 } 8792 } 8793 arr.push(template.substring(start,i)); 8794 // console.log("start is " + start + " i is " + i + " and substr is " + template.substring(start,i)); 8795 } 8796 } 8797 return arr; 8798 }, 8799 8800 /** 8801 * @protected 8802 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 8803 * @param {string} components Format components to search 8804 * @param {string} length Length of the requested format 8805 * @return {string|undefined} the requested format 8806 */ 8807 _getFormatInternal: function getFormatInternal(obj, components, length) { 8808 if (typeof(components) !== 'undefined' && obj && obj[components]) { 8809 return this._getLengthFormat(obj[components], length); 8810 } 8811 return undefined; 8812 }, 8813 8814 // stand-alone of m (month) is l 8815 // stand-alone of d (day) is a 8816 // stand-alone of w (weekday) is e 8817 // stand-alone of y (year) is r 8818 _standAlones: { 8819 "m": "l", 8820 "d": "a", 8821 "w": "e", 8822 "y": "r" 8823 }, 8824 8825 /** 8826 * @protected 8827 * @param {Object.<string, (string|{s:string,m:string,l:string,f:string})>} obj Object to search 8828 * @param {string} components Format components to search 8829 * @param {string} length Length of the requested format 8830 * @return {string|undefined} the requested format 8831 */ 8832 _getFormat: function getFormat(obj, components, length) { 8833 // handle some special cases for stand-alone formats 8834 if (components && this._standAlones[components]) { 8835 var tmp = this._getFormatInternal(obj, this._standAlones[components], length); 8836 if (tmp) { 8837 return tmp; 8838 } 8839 } 8840 8841 // if no stand-alone format is available, fall back to the regular format 8842 return this._getFormatInternal(obj, components, length); 8843 }, 8844 8845 /** 8846 * @protected 8847 * @param {(string|{s:string,m:string,l:string,f:string})} obj Object to search 8848 * @param {string} length Length of the requested format 8849 * @return {(string|undefined)} the requested format 8850 */ 8851 _getLengthFormat: function getLengthFormat(obj, length) { 8852 if (typeof(obj) === 'string') { 8853 return obj; 8854 } else if (obj[length]) { 8855 return obj[length]; 8856 } 8857 return undefined; 8858 }, 8859 8860 /** 8861 * Return the locale used with this formatter instance. 8862 * @return {Locale} the Locale instance for this formatter 8863 */ 8864 getLocale: function() { 8865 return this.locale; 8866 }, 8867 8868 /** 8869 * Return the template string that is used to format date/times for this 8870 * formatter instance. This will work, even when the template property is not explicitly 8871 * given in the options to the constructor. Without the template option, the constructor 8872 * will build the appropriate template according to the options and use that template 8873 * in the format method. 8874 * 8875 * @return {string} the format template for this formatter 8876 */ 8877 getTemplate: function() { 8878 return this.template; 8879 }, 8880 8881 /** 8882 * Return the order of the year, month, and date components for the current locale.<p> 8883 * 8884 * When implementing a date input widget in a UI, it would be useful to know what 8885 * order to put the year, month, and date input fields so that it conforms to the 8886 * user expectations for the locale. This method gives that order by returning a 8887 * string that has a single "y", "m", and "d" character in it in the correct 8888 * order.<p> 8889 * 8890 * For example, the return value "ymd" means that this locale formats the year first, 8891 * the month second, and the date third, and "mdy" means that the month is first, 8892 * the date is second, and the year is third. Four of the 6 possible permutations 8893 * of the three letters have at least one locale that uses that ordering, though some 8894 * combinations are far more likely than others. The ones that are not used by any 8895 * locales are "dym" and "myd", though new locales are still being added to 8896 * CLDR frequently, and possible orderings cannot be predicted. Your code should 8897 * support all 6 possibilities, just in case. 8898 * 8899 * @return {string} a string giving the date component order 8900 */ 8901 getDateComponentOrder: function() { 8902 return this.componentOrder; 8903 }, 8904 8905 /** 8906 * Return the type of this formatter. The type is a string that has one of the following 8907 * values: "time", "date", "datetime". 8908 * @return {string} the type of the formatter 8909 */ 8910 getType: function() { 8911 return this.type; 8912 }, 8913 8914 /** 8915 * Return the name of the calendar used to format date/times for this 8916 * formatter instance. 8917 * @return {string} the name of the calendar used by this formatter 8918 */ 8919 getCalendar: function () { 8920 return this.cal.getType(); 8921 }, 8922 8923 /** 8924 * Return the length used to format date/times in this formatter. This is either the 8925 * value of the length option to the constructor, or the default value. 8926 * 8927 * @return {string} the length of formats this formatter returns 8928 */ 8929 getLength: function () { 8930 return DateFmt.lenmap[this.length] || ""; 8931 }, 8932 8933 /** 8934 * Return the date components that this formatter formats. This is either the 8935 * value of the date option to the constructor, or the default value. If this 8936 * formatter is a time-only formatter, this method will return the empty 8937 * string. The date component letters may be specified in any order in the 8938 * constructor, but this method will reorder the given components to a standard 8939 * order. 8940 * 8941 * @return {string} the date components that this formatter formats 8942 */ 8943 getDateComponents: function () { 8944 return this.dateComponents || ""; 8945 }, 8946 8947 /** 8948 * Return the time components that this formatter formats. This is either the 8949 * value of the time option to the constructor, or the default value. If this 8950 * formatter is a date-only formatter, this method will return the empty 8951 * string. The time component letters may be specified in any order in the 8952 * constructor, but this method will reorder the given components to a standard 8953 * order. 8954 * 8955 * @return {string} the time components that this formatter formats 8956 */ 8957 getTimeComponents: function () { 8958 return this.timeComponents || ""; 8959 }, 8960 8961 /** 8962 * Return the time zone used to format date/times for this formatter 8963 * instance. 8964 * @return {TimeZone} a time zone object that this formatter is formatting for 8965 */ 8966 getTimeZone: function () { 8967 return this.tz; 8968 }, 8969 8970 /** 8971 * Return the clock option set in the constructor. If the clock option was 8972 * not given, the default from the locale is returned instead. 8973 * @return {string} "12" or "24" depending on whether this formatter uses 8974 * the 12-hour or 24-hour clock 8975 */ 8976 getClock: function () { 8977 return this.clock || this.locinfo.getClock(); 8978 }, 8979 8980 /** 8981 * Return the meridiems range in current locale. 8982 * @return {Array.<{name:string,start:string,end:string}>} the range of available meridiems 8983 */ 8984 getMeridiemsRange: function () { 8985 var result; 8986 var _getSysString = function (key) { 8987 return (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)).toString(); 8988 }; 8989 8990 switch (this.meridiems) { 8991 case "chinese": 8992 result = [ 8993 { 8994 name: _getSysString.call(this, "azh0"), 8995 start: "00:00", 8996 end: "05:59" 8997 }, 8998 { 8999 name: _getSysString.call(this, "azh1"), 9000 start: "06:00", 9001 end: "08:59" 9002 }, 9003 { 9004 name: _getSysString.call(this, "azh2"), 9005 start: "09:00", 9006 end: "11:59" 9007 }, 9008 { 9009 name: _getSysString.call(this, "azh3"), 9010 start: "12:00", 9011 end: "12:59" 9012 }, 9013 { 9014 name: _getSysString.call(this, "azh4"), 9015 start: "13:00", 9016 end: "17:59" 9017 }, 9018 { 9019 name: _getSysString.call(this, "azh5"), 9020 start: "18:00", 9021 end: "20:59" 9022 }, 9023 { 9024 name: _getSysString.call(this, "azh6"), 9025 start: "21:00", 9026 end: "23:59" 9027 } 9028 ]; 9029 break; 9030 case "ethiopic": 9031 result = [ 9032 { 9033 name: _getSysString.call(this, "a0-ethiopic"), 9034 start: "00:00", 9035 end: "05:59" 9036 }, 9037 { 9038 name: _getSysString.call(this, "a1-ethiopic"), 9039 start: "06:00", 9040 end: "06:00" 9041 }, 9042 { 9043 name: _getSysString.call(this, "a2-ethiopic"), 9044 start: "06:01", 9045 end: "11:59" 9046 }, 9047 { 9048 name: _getSysString.call(this, "a3-ethiopic"), 9049 start: "12:00", 9050 end: "17:59" 9051 }, 9052 { 9053 name: _getSysString.call(this, "a4-ethiopic"), 9054 start: "18:00", 9055 end: "23:59" 9056 } 9057 ]; 9058 break; 9059 default: 9060 result = [ 9061 { 9062 name: _getSysString.call(this, "a0"), 9063 start: "00:00", 9064 end: "11:59" 9065 }, 9066 { 9067 name: _getSysString.call(this, "a1"), 9068 start: "12:00", 9069 end: "23:59" 9070 } 9071 ]; 9072 break; 9073 } 9074 9075 return result; 9076 }, 9077 9078 /** 9079 * @private 9080 */ 9081 _getTemplate: function (prefix, calendar) { 9082 if (calendar !== "gregorian") { 9083 return prefix + "-" + calendar; 9084 } 9085 return prefix; 9086 }, 9087 9088 /** 9089 * Returns an array of the months of the year, formatted to the optional length specified. 9090 * i.e. ...getMonthsOfYear() OR ...getMonthsOfYear({length: "short"}) 9091 * <p> 9092 * The options parameter may contain any of the following properties: 9093 * 9094 * <ul> 9095 * <li><i>length</i> - length of the names of the months being sought. This may be one of 9096 * "short", "medium", "long", or "full" 9097 * <li><i>date</i> - retrieve the names of the months in the date of the given date 9098 * <li><i>year</i> - retrieve the names of the months in the given year. In some calendars, 9099 * the months have different names depending if that year is a leap year or not. 9100 * </ul> 9101 * 9102 * @param {Object=} options an object-literal that contains any of the above properties 9103 * @return {Array} an array of the names of all of the months of the year in the current calendar 9104 */ 9105 getMonthsOfYear: function(options) { 9106 var length = (options && options.length) || this.getLength(), 9107 template = DateFmt.monthNameLenMap[length], 9108 months = [undefined], 9109 date, 9110 monthCount; 9111 9112 if (options) { 9113 if (options.date) { 9114 date = DateFactory._dateToIlib(options.date); 9115 } 9116 9117 if (options.year) { 9118 date = DateFactory({year: options.year, month: 1, day: 1, type: this.cal.getType()}); 9119 } 9120 } 9121 9122 if (!date) { 9123 date = DateFactory({ 9124 calendar: this.cal.getType() 9125 }); 9126 } 9127 9128 monthCount = this.cal.getNumMonths(date.getYears()); 9129 for (var i = 1; i <= monthCount; i++) { 9130 months[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 9131 } 9132 return months; 9133 }, 9134 9135 /** 9136 * Returns an array of the days of the week, formatted to the optional length specified. 9137 * i.e. ...getDaysOfWeek() OR ...getDaysOfWeek({length: "short"}) 9138 * <p> 9139 * The options parameter may contain any of the following properties: 9140 * 9141 * <ul> 9142 * <li><i>length</i> - length of the names of the months being sought. This may be one of 9143 * "short", "medium", "long", or "full" 9144 * </ul> 9145 * @param {Object=} options an object-literal that contains one key 9146 * "length" with the standard length strings 9147 * @return {Array} an array of all of the names of the days of the week 9148 */ 9149 getDaysOfWeek: function(options) { 9150 var length = (options && options.length) || this.getLength(), 9151 template = DateFmt.weekDayLenMap[length], 9152 days = []; 9153 for (var i = 0; i < 7; i++) { 9154 days[i] = this.sysres.getString(this._getTemplate(template + i, this.cal.getType())).toString(); 9155 } 9156 return days; 9157 }, 9158 9159 9160 /** 9161 * Convert this formatter to a string representation by returning the 9162 * format template. This method delegates to getTemplate. 9163 * 9164 * @return {string} the format template 9165 */ 9166 toString: function() { 9167 return this.getTemplate(); 9168 }, 9169 9170 /** 9171 * @private 9172 * Format a date according to a sequence of components. 9173 * @param {IDate} date a date/time object to format 9174 * @param {Array.<string>} templateArr an array of components to format 9175 * @return {string} the formatted date 9176 */ 9177 _formatTemplate: function (date, templateArr) { 9178 var i, key, temp, tz, str = ""; 9179 for (i = 0; i < templateArr.length; i++) { 9180 switch (templateArr[i]) { 9181 case 'd': 9182 str += (date.day || 1); 9183 break; 9184 case 'dd': 9185 str += JSUtils.pad(date.day || "1", 2); 9186 break; 9187 case 'yy': 9188 temp = "" + ((date.year || 0) % 100); 9189 str += JSUtils.pad(temp, 2); 9190 break; 9191 case 'yyyy': 9192 str += JSUtils.pad(date.year || "0", 4); 9193 break; 9194 case 'M': 9195 str += (date.month || 1); 9196 break; 9197 case 'MM': 9198 str += JSUtils.pad(date.month || "1", 2); 9199 break; 9200 case 'h': 9201 temp = (date.hour || 0) % 12; 9202 if (temp == 0) { 9203 temp = "12"; 9204 } 9205 str += temp; 9206 break; 9207 case 'hh': 9208 temp = (date.hour || 0) % 12; 9209 if (temp == 0) { 9210 temp = "12"; 9211 } 9212 str += JSUtils.pad(temp, 2); 9213 break; 9214 /* 9215 case 'j': 9216 temp = (date.hour || 0) % 12 + 1; 9217 str += temp; 9218 break; 9219 case 'jj': 9220 temp = (date.hour || 0) % 12 + 1; 9221 str += JSUtils.pad(temp, 2); 9222 break; 9223 */ 9224 case 'K': 9225 temp = (date.hour || 0) % 12; 9226 str += temp; 9227 break; 9228 case 'KK': 9229 temp = (date.hour || 0) % 12; 9230 str += JSUtils.pad(temp, 2); 9231 break; 9232 9233 case 'H': 9234 str += (date.hour || "0"); 9235 break; 9236 case 'HH': 9237 str += JSUtils.pad(date.hour || "0", 2); 9238 break; 9239 case 'k': 9240 str += (date.hour == 0 ? "24" : date.hour); 9241 break; 9242 case 'kk': 9243 temp = (date.hour == 0 ? "24" : date.hour); 9244 str += JSUtils.pad(temp, 2); 9245 break; 9246 9247 case 'm': 9248 str += (date.minute || "0"); 9249 break; 9250 case 'mm': 9251 str += JSUtils.pad(date.minute || "0", 2); 9252 break; 9253 case 's': 9254 str += (date.second || "0"); 9255 break; 9256 case 'ss': 9257 str += JSUtils.pad(date.second || "0", 2); 9258 break; 9259 case 'S': 9260 str += (date.millisecond || "0"); 9261 break; 9262 case 'SSS': 9263 str += JSUtils.pad(date.millisecond || "0", 3); 9264 break; 9265 9266 case 'N': 9267 case 'NN': 9268 case 'MMM': 9269 case 'MMMM': 9270 case 'L': 9271 case 'LL': 9272 case 'LLL': 9273 case 'LLLL': 9274 key = templateArr[i] + (date.month || 1); 9275 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9276 break; 9277 9278 case 'E': 9279 case 'EE': 9280 case 'EEE': 9281 case 'EEEE': 9282 case 'c': 9283 case 'cc': 9284 case 'ccc': 9285 case 'cccc': 9286 key = templateArr[i] + date.getDayOfWeek(); 9287 //console.log("finding " + key + " in the resources"); 9288 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9289 break; 9290 9291 case 'a': 9292 switch (this.meridiems) { 9293 case "chinese": 9294 if (date.hour < 6) { 9295 key = "azh0"; // before dawn 9296 } else if (date.hour < 9) { 9297 key = "azh1"; // morning 9298 } else if (date.hour < 12) { 9299 key = "azh2"; // late morning/day before noon 9300 } else if (date.hour < 13) { 9301 key = "azh3"; // noon hour/midday 9302 } else if (date.hour < 18) { 9303 key = "azh4"; // afternoon 9304 } else if (date.hour < 21) { 9305 key = "azh5"; // evening time/dusk 9306 } else { 9307 key = "azh6"; // night time 9308 } 9309 break; 9310 case "ethiopic": 9311 if (date.hour < 6) { 9312 key = "a0-ethiopic"; // morning 9313 } else if (date.hour === 6 && date.minute === 0) { 9314 key = "a1-ethiopic"; // noon 9315 } else if (date.hour >= 6 && date.hour < 12) { 9316 key = "a2-ethiopic"; // afternoon 9317 } else if (date.hour >= 12 && date.hour < 18) { 9318 key = "a3-ethiopic"; // evening 9319 } else if (date.hour >= 18) { 9320 key = "a4-ethiopic"; // night 9321 } 9322 break; 9323 default: 9324 key = date.hour < 12 ? "a0" : "a1"; 9325 break; 9326 } 9327 //console.log("finding " + key + " in the resources"); 9328 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9329 break; 9330 9331 case 'w': 9332 str += date.getWeekOfYear(); 9333 break; 9334 case 'ww': 9335 str += JSUtils.pad(date.getWeekOfYear(), 2); 9336 break; 9337 9338 case 'D': 9339 str += date.getDayOfYear(); 9340 break; 9341 case 'DD': 9342 str += JSUtils.pad(date.getDayOfYear(), 2); 9343 break; 9344 case 'DDD': 9345 str += JSUtils.pad(date.getDayOfYear(), 3); 9346 break; 9347 case 'W': 9348 str += date.getWeekOfMonth(this.locale); 9349 break; 9350 9351 case 'G': 9352 key = "G" + date.getEra(); 9353 str += (this.sysres.getString(undefined, key + "-" + this.calName) || this.sysres.getString(undefined, key)); 9354 break; 9355 9356 case 'O': 9357 temp = this.sysres.getString("1#1st|2#2nd|3#3rd|21#21st|22#22nd|23#23rd|31#31st|#{num}th", "ordinalChoice"); 9358 str += temp.formatChoice(date.day, {num: date.day}); 9359 break; 9360 9361 case 'z': // general time zone 9362 tz = this.getTimeZone(); // lazy-load the tz 9363 str += tz.getDisplayName(date, "standard"); 9364 break; 9365 case 'Z': // RFC 822 time zone 9366 tz = this.getTimeZone(); // lazy-load the tz 9367 str += tz.getDisplayName(date, "rfc822"); 9368 break; 9369 9370 default: 9371 str += templateArr[i].replace(/'/g, ""); 9372 break; 9373 } 9374 } 9375 9376 if (this.digits) { 9377 str = JSUtils.mapString(str, this.digits); 9378 } 9379 return str; 9380 }, 9381 9382 /** 9383 * Format a particular date instance according to the settings of this 9384 * formatter object. The type of the date instance being formatted must 9385 * correspond exactly to the calendar type with which this formatter was 9386 * constructed. If the types are not compatible, this formatter will 9387 * produce bogus results. 9388 * 9389 * @param {IDate|number|string|Date|JulianDay|null|undefined} dateLike a date-like object to format 9390 * @return {string} the formatted version of the given date instance 9391 */ 9392 format: function (dateLike) { 9393 var thisZoneName = this.tz && this.tz.getId() || "local"; 9394 9395 var date = DateFactory._dateToIlib(dateLike, thisZoneName, this.locale); 9396 9397 if (!date.getCalendar || !(date instanceof IDate)) { 9398 throw "Wrong date type passed to DateFmt.format()"; 9399 } 9400 9401 var dateZoneName = date.timezone || "local"; 9402 9403 // convert to the time zone of this formatter before formatting 9404 if (dateZoneName !== thisZoneName || date.getCalendar() !== this.calName) { 9405 // console.log("Differing time zones date: " + dateZoneName + " and fmt: " + thisZoneName + ". Converting..."); 9406 // this will recalculate the date components based on the new time zone 9407 // and/or convert a date in another calendar to the current calendar before formatting it 9408 var newDate = DateFactory({ 9409 type: this.calName, 9410 timezone: thisZoneName, 9411 julianday: date.getJulianDay() 9412 }); 9413 9414 date = newDate; 9415 } 9416 return this._formatTemplate(date, this.templateArr); 9417 }, 9418 9419 /** 9420 * Return a string that describes a date relative to the given 9421 * reference date. The string returned is text that for the locale that 9422 * was specified when the formatter instance was constructed.<p> 9423 * 9424 * The date can be in the future relative to the reference date or in 9425 * the past, and the formatter will generate the appropriate string.<p> 9426 * 9427 * The text used to describe the relative reference depends on the length 9428 * of time between the date and the reference. If the time was in the 9429 * past, it will use the "ago" phrase, and in the future, it will use 9430 * the "in" phrase. Examples:<p> 9431 * 9432 * <ul> 9433 * <li>within a minute: either "X seconds ago" or "in X seconds" 9434 * <li>within an hour: either "X minutes ago" or "in X minutes" 9435 * <li>within a day: either "X hours ago" or "in X hours" 9436 * <li>within 2 weeks: either "X days ago" or "in X days" 9437 * <li>within 12 weeks (~3 months): either "X weeks ago" or "in X weeks" 9438 * <li>within two years: either "X months ago" or "in X months" 9439 * <li>longer than 2 years: "X years ago" or "in X years" 9440 * </ul> 9441 * 9442 * @param {IDate|number|string|Date|JulianDay|null|undefined} reference a date that the date parameter should be relative to 9443 * @param {IDate|number|string|Date|JulianDay|null|undefined} date a date being formatted 9444 * @throws "Wrong calendar type" when the start or end dates are not the same 9445 * calendar type as the formatter itself 9446 * @return {string} the formatted relative date 9447 */ 9448 formatRelative: function(reference, date) { 9449 reference = DateFactory._dateToIlib(reference); 9450 date = DateFactory._dateToIlib(date); 9451 9452 var referenceRd, dateRd, fmt, diff, absDiff, num; 9453 9454 if (typeof(reference) !== 'object' || !reference.getCalendar || reference.getCalendar() !== this.calName || 9455 typeof(date) !== 'object' || !date.getCalendar || date.getCalendar() !== this.calName) { 9456 throw "Wrong calendar type"; 9457 } 9458 9459 referenceRd = reference.getRataDie(); 9460 dateRd = date.getRataDie(); 9461 9462 diff = referenceRd - dateRd; 9463 absDiff = Math.abs(diff); 9464 9465 if (absDiff < 0.000694444) { 9466 num = Math.round(absDiff * 86400); 9467 switch (this.length) { 9468 case 's': 9469 fmt = diff > 0 ? this.sysres.getString("#{num}s ago") : this.sysres.getString("#in {num}s"); 9470 break; 9471 case 'm': 9472 fmt = diff > 0 ? this.sysres.getString("1#1 sec ago|#{num} sec ago") : this.sysres.getString("1#in 1 sec|#in {num} sec"); 9473 break; 9474 default: 9475 case 'f': 9476 case 'l': 9477 fmt = diff > 0 ? this.sysres.getString("1#1 second ago|#{num} seconds ago") : this.sysres.getString("1#in 1 second|#in {num} seconds"); 9478 break; 9479 } 9480 } else if (absDiff < 0.041666667) { 9481 num = Math.round(absDiff * 1440); 9482 switch (this.length) { 9483 case 's': 9484 fmt = diff > 0 ? this.sysres.getString("#{num}mi ago") : this.sysres.getString("#in {num}mi"); 9485 break; 9486 case 'm': 9487 fmt = diff > 0 ? this.sysres.getString("1#1 min ago|#{num} min ago") : this.sysres.getString("1#in 1 min|#in {num} min"); 9488 break; 9489 default: 9490 case 'f': 9491 case 'l': 9492 fmt = diff > 0 ? this.sysres.getString("1#1 minute ago|#{num} minutes ago") : this.sysres.getString("1#in 1 minute|#in {num} minutes"); 9493 break; 9494 } 9495 } else if (absDiff < 1) { 9496 num = Math.round(absDiff * 24); 9497 switch (this.length) { 9498 case 's': 9499 fmt = diff > 0 ? this.sysres.getString("#{num}h ago") : this.sysres.getString("#in {num}h"); 9500 break; 9501 case 'm': 9502 fmt = diff > 0 ? this.sysres.getString("1#1 hr ago|#{num} hrs ago") : this.sysres.getString("1#in 1 hr|#in {num} hrs"); 9503 break; 9504 default: 9505 case 'f': 9506 case 'l': 9507 fmt = diff > 0 ? this.sysres.getString("1#1 hour ago|#{num} hours ago") : this.sysres.getString("1#in 1 hour|#in {num} hours"); 9508 break; 9509 } 9510 } else if (absDiff < 14) { 9511 num = Math.round(absDiff); 9512 switch (this.length) { 9513 case 's': 9514 fmt = diff > 0 ? this.sysres.getString("#{num}d ago") : this.sysres.getString("#in {num}d"); 9515 break; 9516 case 'm': 9517 fmt = diff > 0 ? this.sysres.getString("1#1 dy ago|#{nudurationm} dys ago") : this.sysres.getString("1#in 1 dy|#in {num} dys"); 9518 break; 9519 default: 9520 case 'f': 9521 case 'l': 9522 fmt = diff > 0 ? this.sysres.getString("1#1 day ago|#{num} days ago") : this.sysres.getString("1#in 1 day|#in {num} days"); 9523 break; 9524 } 9525 } else if (absDiff < 84) { 9526 num = Math.round(absDiff/7); 9527 switch (this.length) { 9528 case 's': 9529 fmt = diff > 0 ? this.sysres.getString("#{num}w ago") : this.sysres.getString("#in {num}w"); 9530 break; 9531 case 'm': 9532 fmt = diff > 0 ? this.sysres.getString("1#1 wk ago|#{num} wks ago") : this.sysres.getString("1#in 1 wk|#in {num} wks"); 9533 break; 9534 default: 9535 case 'f': 9536 case 'l': 9537 fmt = diff > 0 ? this.sysres.getString("1#1 week ago|#{num} weeks ago") : this.sysres.getString("1#in 1 week|#in {num} weeks"); 9538 break; 9539 } 9540 } else if (absDiff < 730) { 9541 num = Math.round(absDiff/30.4); 9542 switch (this.length) { 9543 case 's': 9544 fmt = diff > 0 ? this.sysres.getString("#{num}mo ago") : this.sysres.getString("#in {num}mo"); 9545 break; 9546 case 'm': 9547 fmt = diff > 0 ? this.sysres.getString("1#1 mon ago|#{num} mons ago") : this.sysres.getString("1#in 1 mon|#in {num} mons"); 9548 break; 9549 default: 9550 case 'f': 9551 case 'l': 9552 fmt = diff > 0 ? this.sysres.getString("1#1 month ago|#{num} months ago") : this.sysres.getString("1#in 1 month|#in {num} months"); 9553 break; 9554 } 9555 } else { 9556 num = Math.round(absDiff/365); 9557 switch (this.length) { 9558 case 's': 9559 fmt = diff > 0 ? this.sysres.getString("#{num}y ago") : this.sysres.getString("#in {num}y"); 9560 break; 9561 case 'm': 9562 fmt = diff > 0 ? this.sysres.getString("1#1 yr ago|#{num} yrs ago") : this.sysres.getString("1#in 1 yr|#in {num} yrs"); 9563 break; 9564 default: 9565 case 'f': 9566 case 'l': 9567 fmt = diff > 0 ? this.sysres.getString("1#1 year ago|#{num} years ago") : this.sysres.getString("1#in 1 year|#in {num} years"); 9568 break; 9569 } 9570 } 9571 return fmt.formatChoice(num, {num: num}); 9572 } 9573 }; 9574 9575 9576 9577 /*< DateRngFmt.js */ 9578 /* 9579 * DateRngFmt.js - Date formatter definition 9580 * 9581 * Copyright © 2012-2015,2018, JEDLSoft 9582 * 9583 * Licensed under the Apache License, Version 2.0 (the "License"); 9584 * you may not use this file except in compliance with the License. 9585 * You may obtain a copy of the License at 9586 * 9587 * http://www.apache.org/licenses/LICENSE-2.0 9588 * 9589 * Unless required by applicable law or agreed to in writing, software 9590 * distributed under the License is distributed on an "AS IS" BASIS, 9591 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9592 * 9593 * See the License for the specific language governing permissions and 9594 * limitations under the License. 9595 */ 9596 9597 /* 9598 !depends 9599 ilib.js 9600 Locale.js 9601 IString.js 9602 CalendarFactory.js 9603 DateFmt.js 9604 GregorianCal.js 9605 JSUtils.js 9606 DateFactory.js 9607 */ 9608 9609 // !data dateformats sysres 9610 9611 9612 9613 9614 9615 9616 /** 9617 * @class 9618 * Create a new date range formatter instance. The date range formatter is immutable once 9619 * it is created, but can format as many different date ranges as needed with the same 9620 * options. Create different date range formatter instances for different purposes 9621 * and then keep them cached for use later if you have more than one range to 9622 * format.<p> 9623 * 9624 * The options may contain any of the following properties: 9625 * 9626 * <ul> 9627 * <li><i>locale</i> - locale to use when formatting the date/times in the range. If the 9628 * locale is not specified, then the default locale of the app or web page will be used. 9629 * 9630 * <li><i>calendar</i> - the type of calendar to use for this format. The value should 9631 * be a sting containing the name of the calendar. Currently, the supported 9632 * types are "gregorian", "julian", "arabic", "hebrew", or "chinese". If the 9633 * calendar is not specified, then the default calendar for the locale is used. When the 9634 * calendar type is specified, then the format method must be called with an instance of 9635 * the appropriate date type. (eg. Gregorian calendar means that the format method must 9636 * be called with a GregDate instance.) 9637 * 9638 * <li><i>timezone</i> - time zone to use when formatting times. This may be a time zone 9639 * instance or a time zone specifier string in RFC 822 format. If not specified, the 9640 * default time zone for the locale is used. 9641 * 9642 * <li><i>length</i> - Specify the length of the format to use as a string. The length 9643 * is the approximate size of the formatted string. 9644 * 9645 * <ul> 9646 * <li><i>short</i> - use a short representation of the time. This is the most compact format possible for the locale. 9647 * <li><i>medium</i> - use a medium length representation of the time. This is a slightly longer format. 9648 * <li><i>long</i> - use a long representation of the time. This is a fully specified format, but some of the textual 9649 * components may still be abbreviated. (eg. "Tue" instead of "Tuesday") 9650 * <li><i>full</i> - use a full representation of the time. This is a fully specified format where all the textual 9651 * components are spelled out completely. 9652 * </ul> 9653 * 9654 * eg. The "short" format for an en_US range may be "MM/yy - MM/yy", whereas the long format might be 9655 * "MMM, yyyy - MMM, yyyy". In the long format, the month name is textual instead of numeric 9656 * and is longer, the year is 4 digits instead of 2, and the format contains slightly more 9657 * spaces and formatting characters.<p> 9658 * 9659 * Note that the length parameter does not specify which components are to be formatted. The 9660 * components that are formatted depend on the length of time in the range. 9661 * 9662 * <li><i>clock</i> - specify that formatted times should use a 12 or 24 hour clock if the 9663 * format happens to include times. Valid values are "12" and "24".<p> 9664 * 9665 * In some locales, both clocks are used. For example, in en_US, the general populace uses 9666 * a 12 hour clock with am/pm, but in the US military or in nautical or aeronautical or 9667 * scientific writing, it is more common to use a 24 hour clock. This property allows you to 9668 * construct a formatter that overrides the default for the locale.<p> 9669 * 9670 * If this property is not specified, the default is to use the most widely used convention 9671 * for the locale. 9672 * <li>onLoad - a callback function to call when the date range format object is fully 9673 * loaded. When the onLoad option is given, the DateRngFmt object will attempt to 9674 * load any missing locale data using the ilib loader callback. 9675 * When the constructor is done (even if the data is already preassembled), the 9676 * onLoad function is called with the current instance as a parameter, so this 9677 * callback can be used with preassembled or dynamic loading or a mix of the two. 9678 * 9679 * <li>sync - tell whether to load any missing locale data synchronously or 9680 * asynchronously. If this option is given as "false", then the "onLoad" 9681 * callback must be given, as the instance returned from this constructor will 9682 * not be usable for a while. 9683 * 9684 * <li><i>loadParams</i> - an object containing parameters to pass to the 9685 * loader callback function when locale data is missing. The parameters are not 9686 * interpretted or modified in any way. They are simply passed along. The object 9687 * may contain any property/value pairs as long as the calling code is in 9688 * agreement with the loader callback function as to what those parameters mean. 9689 * </ul> 9690 * <p> 9691 * 9692 * 9693 * @constructor 9694 * @param {Object} options options governing the way this date range formatter instance works 9695 */ 9696 var DateRngFmt = function(options) { 9697 var sync = true; 9698 var loadParams = undefined; 9699 this.locale = new Locale(); 9700 this.length = "s"; 9701 9702 if (options) { 9703 if (options.locale) { 9704 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 9705 } 9706 9707 if (options.calendar) { 9708 this.calName = options.calendar; 9709 } 9710 9711 if (options.length) { 9712 if (options.length === 'short' || 9713 options.length === 'medium' || 9714 options.length === 'long' || 9715 options.length === 'full') { 9716 // only use the first char to save space in the json files 9717 this.length = options.length.charAt(0); 9718 } 9719 } 9720 if (typeof(options.sync) !== 'undefined') { 9721 sync = !!options.sync; 9722 } 9723 9724 loadParams = options.loadParams; 9725 } 9726 9727 var opts = {}; 9728 JSUtils.shallowCopy(options, opts); 9729 opts.sync = sync; 9730 opts.loadParams = loadParams; 9731 9732 /** 9733 * @private 9734 */ 9735 opts.onLoad = ilib.bind(this, function (fmt) { 9736 this.dateFmt = fmt; 9737 if (fmt) { 9738 this.locinfo = this.dateFmt.locinfo; 9739 9740 // get the default calendar name from the locale, and if the locale doesn't define 9741 // one, use the hard-coded gregorian as the last resort 9742 this.calName = this.calName || this.locinfo.getCalendar() || "gregorian"; 9743 CalendarFactory({ 9744 type: this.calName, 9745 sync: sync, 9746 loadParams: loadParams, 9747 onLoad: ilib.bind(this, function(cal) { 9748 this.cal = cal; 9749 9750 if (!this.cal) { 9751 // always synchronous 9752 this.cal = new GregorianCal(); 9753 } 9754 9755 this.timeTemplate = this.dateFmt._getFormat(this.dateFmt.formats.time[this.dateFmt.clock], this.dateFmt.timeComponents, this.length) || "hh:mm"; 9756 this.timeTemplateArr = this.dateFmt._tokenize(this.timeTemplate); 9757 9758 if (options && typeof(options.onLoad) === 'function') { 9759 options.onLoad(this); 9760 } 9761 }) 9762 }); 9763 } else { 9764 if (options && typeof(options.sync) === "boolean" && !options.sync && typeof(options.onLoad) === 'function') { 9765 options.onLoad(undefined); 9766 } else { 9767 throw "No formats available for calendar " + this.calName + " in locale " + this.locale.getSpec(); 9768 } 9769 } 9770 }); 9771 9772 // delegate a bunch of the formatting to this formatter 9773 new DateFmt(opts); 9774 }; 9775 9776 DateRngFmt.prototype = { 9777 /** 9778 * Return the locale used with this formatter instance. 9779 * @return {Locale} the Locale instance for this formatter 9780 */ 9781 getLocale: function() { 9782 return this.locale; 9783 }, 9784 9785 /** 9786 * Return the name of the calendar used to format date/times for this 9787 * formatter instance. 9788 * @return {string} the name of the calendar used by this formatter 9789 */ 9790 getCalendar: function () { 9791 return this.dateFmt.getCalendar(); 9792 }, 9793 9794 /** 9795 * Return the length used to format date/times in this formatter. This is either the 9796 * value of the length option to the constructor, or the default value. 9797 * 9798 * @return {string} the length of formats this formatter returns 9799 */ 9800 getLength: function () { 9801 return DateFmt.lenmap[this.length] || ""; 9802 }, 9803 9804 /** 9805 * Return the time zone used to format date/times for this formatter 9806 * instance. 9807 * @return {TimeZone} a string naming the time zone 9808 */ 9809 getTimeZone: function () { 9810 return this.dateFmt.getTimeZone(); 9811 }, 9812 9813 /** 9814 * Return the clock option set in the constructor. If the clock option was 9815 * not given, the default from the locale is returned instead. 9816 * @return {string} "12" or "24" depending on whether this formatter uses 9817 * the 12-hour or 24-hour clock 9818 */ 9819 getClock: function () { 9820 return this.dateFmt.getClock(); 9821 }, 9822 9823 /** 9824 * Format a date/time range according to the settings of the current 9825 * formatter. The range is specified as being from the "start" date until 9826 * the "end" date. <p> 9827 * 9828 * The template that the date/time range uses depends on the 9829 * length of time between the dates, on the premise that a long date range 9830 * which is too specific is not useful. For example, when giving 9831 * the dates of the 100 Years War, in most situations it would be more 9832 * appropriate to format the range as "1337 - 1453" than to format it as 9833 * "10:37am November 9, 1337 - 4:37pm July 17, 1453", as the latter format 9834 * is much too specific given the length of time that the range represents. 9835 * If a very specific, but long, date range really is needed, the caller 9836 * should format two specific dates separately and put them 9837 * together as you might with other normal strings.<p> 9838 * 9839 * The format used for a date range contains the following date components, 9840 * where the order of those components is rearranged and the component values 9841 * are translated according to each locale: 9842 * 9843 * <ul> 9844 * <li>within 3 days: the times of day, dates, months, and years 9845 * <li>within 730 days (2 years): the dates, months, and years 9846 * <li>within 3650 days (10 years): the months and years 9847 * <li>longer than 10 years: the years only 9848 * </ul> 9849 * 9850 * In general, if any of the date components share a value between the 9851 * start and end date, that component is only given once. For example, 9852 * if the range is from November 15, 2011 to November 26, 2011, the 9853 * start and end dates both share the same month and year. The 9854 * range would then be formatted as "November 15-26, 2011". <p> 9855 * 9856 * If you want to format a length of time instead of a particular range of 9857 * time (for example, the length of an event rather than the specific start time 9858 * and end time of that event), then use a duration formatter instance 9859 * (DurationFmt) instead. The formatRange method will make sure that each component 9860 * of the date/time is within the normal range for that component. For example, 9861 * the minutes will always be between 0 and 59, no matter what is specified in 9862 * the date to format, because that is the normal range for minutes. A duration 9863 * format will allow the number of minutes to exceed 59. For example, if you 9864 * were displaying the length of a movie that is 198 minutes long, the minutes 9865 * component of a duration could be 198.<p> 9866 * 9867 * @param {IDate|Date|number|string} startDateLike the starting date/time of the range. The 9868 * date may be given as an ilib IDate object, a javascript intrinsic Date object, a 9869 * unix time, or a date string parsable by the javscript Date. 9870 * @param {IDate|Date|number|string} endDateLike the ending date/time of the range. The 9871 * date may be given as an ilib IDate object, a javascript intrinsic Date object, a 9872 * unix time, or a date string parsable by the javscript Date. 9873 * @throws "Wrong calendar type" when the start or end dates are not the same 9874 * calendar type as the formatter itself 9875 * @return {string} a date range formatted for the locale 9876 */ 9877 format: function (startDateLike, endDateLike) { 9878 var startRd, endRd, fmt = "", yearTemplate, monthTemplate, dayTemplate, formats; 9879 var thisZoneName = this.dateFmt.tz && this.dateFmt.tz.getId() || "local"; 9880 9881 var start = DateFactory._dateToIlib(startDateLike, thisZoneName, this.locale); 9882 var end = DateFactory._dateToIlib(endDateLike, thisZoneName, this.locale); 9883 9884 if (typeof(start) !== 'object' || !start.getCalendar || start.getCalendar() !== this.calName || 9885 typeof(end) !== 'object' || !end.getCalendar || end.getCalendar() !== this.calName) { 9886 throw "Wrong calendar type"; 9887 } 9888 9889 startRd = start.getRataDie(); 9890 endRd = end.getRataDie(); 9891 9892 // 9893 // legend: 9894 // c00 - difference is less than 3 days. Year, month, and date are same, but time is different 9895 // c01 - difference is less than 3 days. Year and month are same but date and time are different 9896 // c02 - difference is less than 3 days. Year is same but month, date, and time are different. (ie. it straddles a month boundary) 9897 // c03 - difference is less than 3 days. Year, month, date, and time are all different. (ie. it straddles a year boundary) 9898 // c10 - difference is less than 2 years. Year and month are the same, but date is different. 9899 // c11 - difference is less than 2 years. Year is the same, but month, date, and time are different. 9900 // c12 - difference is less than 2 years. All fields are different. (ie. straddles a year boundary) 9901 // c20 - difference is less than 10 years. All fields are different. 9902 // c30 - difference is more than 10 years. All fields are different. 9903 // 9904 9905 if (endRd - startRd < 3) { 9906 if (start.year === end.year) { 9907 if (start.month === end.month) { 9908 if (start.day === end.day) { 9909 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c00", this.length)); 9910 } else { 9911 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c01", this.length)); 9912 } 9913 } else { 9914 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c02", this.length)); 9915 } 9916 } else { 9917 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c03", this.length)); 9918 } 9919 } else if (endRd - startRd < 730) { 9920 if (start.year === end.year) { 9921 if (start.month === end.month) { 9922 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c10", this.length)); 9923 } else { 9924 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c11", this.length)); 9925 } 9926 } else { 9927 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c12", this.length)); 9928 } 9929 } else if (endRd - startRd < 3650) { 9930 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c20", this.length)); 9931 } else { 9932 fmt = new IString(this.dateFmt._getFormat(this.dateFmt.formats.range, "c30", this.length)); 9933 } 9934 9935 formats = this.dateFmt.formats.date; 9936 yearTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "y", this.length) || "yyyy"); 9937 monthTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "m", this.length) || "MM"); 9938 dayTemplate = this.dateFmt._tokenize(this.dateFmt._getFormatInternal(formats, "d", this.length) || "dd"); 9939 9940 /* 9941 console.log("fmt is " + fmt.toString()); 9942 console.log("year template is " + yearTemplate); 9943 console.log("month template is " + monthTemplate); 9944 console.log("day template is " + dayTemplate); 9945 */ 9946 9947 return fmt.format({ 9948 sy: this.dateFmt._formatTemplate(start, yearTemplate), 9949 sm: this.dateFmt._formatTemplate(start, monthTemplate), 9950 sd: this.dateFmt._formatTemplate(start, dayTemplate), 9951 st: this.dateFmt._formatTemplate(start, this.timeTemplateArr), 9952 ey: this.dateFmt._formatTemplate(end, yearTemplate), 9953 em: this.dateFmt._formatTemplate(end, monthTemplate), 9954 ed: this.dateFmt._formatTemplate(end, dayTemplate), 9955 et: this.dateFmt._formatTemplate(end, this.timeTemplateArr) 9956 }); 9957 } 9958 }; 9959 9960 9961 /*< HebrewCal.js */ 9962 /* 9963 * hebrew.js - Represent a Hebrew calendar object. 9964 * 9965 * Copyright © 2012-2015,2018, JEDLSoft 9966 * 9967 * Licensed under the Apache License, Version 2.0 (the "License"); 9968 * you may not use this file except in compliance with the License. 9969 * You may obtain a copy of the License at 9970 * 9971 * http://www.apache.org/licenses/LICENSE-2.0 9972 * 9973 * Unless required by applicable law or agreed to in writing, software 9974 * distributed under the License is distributed on an "AS IS" BASIS, 9975 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 9976 * 9977 * See the License for the specific language governing permissions and 9978 * limitations under the License. 9979 */ 9980 9981 9982 /* !depends Calendar.js MathUtils.js */ 9983 9984 9985 /** 9986 * @class 9987 * Construct a new Hebrew calendar object. This class encodes information about 9988 * the Hebrew (Jewish) calendar. The Hebrew calendar is a tabular hebrew 9989 * calendar where the dates are calculated by arithmetic rules. This differs from 9990 * the religious Hebrew calendar which is used to mark the beginning of particular 9991 * holidays. The religious calendar depends on the first sighting of the new 9992 * crescent moon to determine the first day of the new month. Because humans and 9993 * weather are both involved, the actual time of sighting varies, so it is not 9994 * really possible to precalculate the religious calendar. Certain groups, such 9995 * as the Hebrew Society of North America, decreed in in 2007 that they will use 9996 * a calendar based on calculations rather than observations to determine the 9997 * beginning of lunar months, and therefore the dates of holidays.<p> 9998 * 9999 * @param {Object=} options Options governing the construction of this instance 10000 * @constructor 10001 * @extends Calendar 10002 */ 10003 var HebrewCal = function(options) { 10004 this.type = "hebrew"; 10005 10006 if (options && typeof(options.onLoad) === "function") { 10007 options.onLoad(this); 10008 } 10009 }; 10010 10011 /** 10012 * Return the number of days elapsed in the Hebrew calendar before the 10013 * given year starts. 10014 * @private 10015 * @param {number} year the year for which the number of days is sought 10016 * @return {number} the number of days elapsed in the Hebrew calendar before the 10017 * given year starts 10018 */ 10019 HebrewCal.elapsedDays = function(year) { 10020 var months = Math.floor(((235*year) - 234)/19); 10021 var parts = 204 + 793 * MathUtils.mod(months, 1080); 10022 var hours = 11 + 12 * months + 793 * Math.floor(months/1080) + 10023 Math.floor(parts/1080); 10024 var days = 29 * months + Math.floor(hours/24); 10025 return (MathUtils.mod(3 * (days + 1), 7) < 3) ? days + 1 : days; 10026 }; 10027 10028 /** 10029 * Return the number of days that the New Year's (Rosh HaShanah) in the Hebrew 10030 * calendar will be corrected for the given year. Corrections are caused because New 10031 * Year's is not allowed to start on certain days of the week. To deal with 10032 * it, the start of the new year is corrected for the next year by adding a 10033 * day to the 8th month (Heshvan) and/or the 9th month (Kislev) in the current 10034 * year to make them 30 days long instead of 29. 10035 * 10036 * @private 10037 * @param {number} year the year for which the correction is sought 10038 * @param {number} elapsed number of days elapsed up to this year 10039 * @return {number} the number of days correction in the current year to make sure 10040 * Rosh HaShanah does not fall on undesirable days of the week 10041 */ 10042 HebrewCal.newYearsCorrection = function(year, elapsed) { 10043 var lastYear = HebrewCal.elapsedDays(year-1), 10044 thisYear = elapsed, 10045 nextYear = HebrewCal.elapsedDays(year+1); 10046 10047 return (nextYear - thisYear) == 356 ? 2 : ((thisYear - lastYear) == 382 ? 1 : 0); 10048 }; 10049 10050 /** 10051 * Return the rata die date of the new year for the given hebrew year. 10052 * @private 10053 * @param {number} year the year for which the new year is needed 10054 * @return {number} the rata die date of the new year 10055 */ 10056 HebrewCal.newYear = function(year) { 10057 var elapsed = HebrewCal.elapsedDays(year); 10058 10059 return elapsed + HebrewCal.newYearsCorrection(year, elapsed); 10060 }; 10061 10062 /** 10063 * Return the number of days in the given year. Years contain a variable number of 10064 * days because the date of Rosh HaShanah (New Year's) changes so that it doesn't 10065 * fall on particular days of the week. Days are added to the months of Heshvan 10066 * and/or Kislev in the previous year in order to prevent the current year's New 10067 * Year from being on Sunday, Wednesday, or Friday. 10068 * 10069 * @param {number} year the year for which the length is sought 10070 * @return {number} number of days in the given year 10071 */ 10072 HebrewCal.daysInYear = function(year) { 10073 return HebrewCal.newYear(year+1) - HebrewCal.newYear(year); 10074 }; 10075 10076 /** 10077 * Return true if the given year contains a long month of Heshvan. That is, 10078 * it is 30 days instead of 29. 10079 * 10080 * @private 10081 * @param {number} year the year in which that month is questioned 10082 * @return {boolean} true if the given year contains a long month of Heshvan 10083 */ 10084 HebrewCal.longHeshvan = function(year) { 10085 return MathUtils.mod(HebrewCal.daysInYear(year), 10) === 5; 10086 }; 10087 10088 /** 10089 * Return true if the given year contains a long month of Kislev. That is, 10090 * it is 30 days instead of 29. 10091 * 10092 * @private 10093 * @param {number} year the year in which that month is questioned 10094 * @return {boolean} true if the given year contains a short month of Kislev 10095 */ 10096 HebrewCal.longKislev = function(year) { 10097 return MathUtils.mod(HebrewCal.daysInYear(year), 10) !== 3; 10098 }; 10099 10100 /** 10101 * Return the date of the last day of the month for the given year. The date of 10102 * the last day of the month is variable because a number of months gain an extra 10103 * day in leap years, and it is variable which months gain a day for each leap 10104 * year and which do not. 10105 * 10106 * @param {number} month the month for which the number of days is sought 10107 * @param {number} year the year in which that month is 10108 * @return {number} the number of days in the given month and year 10109 */ 10110 HebrewCal.prototype.lastDayOfMonth = function(month, year) { 10111 switch (month) { 10112 case 2: 10113 case 4: 10114 case 6: 10115 case 10: 10116 return 29; 10117 case 13: 10118 return this.isLeapYear(year) ? 29 : 0; 10119 case 8: 10120 return HebrewCal.longHeshvan(year) ? 30 : 29; 10121 case 9: 10122 return HebrewCal.longKislev(year) ? 30 : 29; 10123 case 12: 10124 case 1: 10125 case 3: 10126 case 5: 10127 case 7: 10128 case 11: 10129 return 30; 10130 default: 10131 return 0; 10132 } 10133 }; 10134 10135 /** 10136 * Return the number of months in the given year. The number of months in a year varies 10137 * for luni-solar calendars because in some years, an extra month is needed to extend the 10138 * days in a year to an entire solar year. The month is represented as a 1-based number 10139 * where 1=first month, 2=second month, etc. 10140 * 10141 * @param {number} year a year for which the number of months is sought 10142 */ 10143 HebrewCal.prototype.getNumMonths = function(year) { 10144 return this.isLeapYear(year) ? 13 : 12; 10145 }; 10146 10147 /** 10148 * Return the number of days in a particular month in a particular year. This function 10149 * can return a different number for a month depending on the year because of leap years. 10150 * 10151 * @param {number} month the month for which the length is sought 10152 * @param {number} year the year within which that month can be found 10153 * @returns {number} the number of days within the given month in the given year, or 10154 * 0 for an invalid month in the year 10155 */ 10156 HebrewCal.prototype.getMonLength = function(month, year) { 10157 if (month < 1 || month > 13 || (month == 13 && !this.isLeapYear(year))) { 10158 return 0; 10159 } 10160 return this.lastDayOfMonth(month, year); 10161 }; 10162 10163 /** 10164 * Return true if the given year is a leap year in the Hebrew calendar. 10165 * The year parameter may be given as a number, or as a HebrewDate object. 10166 * @param {number|Object} year the year for which the leap year information is being sought 10167 * @returns {boolean} true if the given year is a leap year 10168 */ 10169 HebrewCal.prototype.isLeapYear = function(year) { 10170 var y = (typeof(year) == 'number') ? year : year.year; 10171 return (MathUtils.mod(1 + 7 * y, 19) < 7); 10172 }; 10173 10174 /** 10175 * Return the type of this calendar. 10176 * 10177 * @returns {string} the name of the type of this calendar 10178 */ 10179 HebrewCal.prototype.getType = function() { 10180 return this.type; 10181 }; 10182 10183 10184 /*register this calendar for the factory method */ 10185 Calendar._constructors["hebrew"] = HebrewCal; 10186 10187 10188 10189 /*< HebrewRataDie.js */ 10190 /* 10191 * HebrewRataDie.js - Represent an RD date in the Hebrew calendar 10192 * 10193 * Copyright © 2012-2015, JEDLSoft 10194 * 10195 * Licensed under the Apache License, Version 2.0 (the "License"); 10196 * you may not use this file except in compliance with the License. 10197 * You may obtain a copy of the License at 10198 * 10199 * http://www.apache.org/licenses/LICENSE-2.0 10200 * 10201 * Unless required by applicable law or agreed to in writing, software 10202 * distributed under the License is distributed on an "AS IS" BASIS, 10203 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10204 * 10205 * See the License for the specific language governing permissions and 10206 * limitations under the License. 10207 */ 10208 10209 /* !depends 10210 MathUtils.js 10211 HebrewCal.js 10212 RataDie.js 10213 */ 10214 10215 10216 /** 10217 * @class 10218 * Construct a new Hebrew RD date number object. The constructor parameters can 10219 * contain any of the following properties: 10220 * 10221 * <ul> 10222 * <li><i>unixtime<i> - sets the time of this instance according to the given 10223 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 10224 * 10225 * <li><i>julianday</i> - sets the time of this instance according to the given 10226 * Julian Day instance or the Julian Day given as a float 10227 * 10228 * <li><i>year</i> - any integer, including 0 10229 * 10230 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 10231 * 10232 * <li><i>day</i> - 1 to 31 10233 * 10234 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10235 * is always done with an unambiguous 24 hour representation 10236 * 10237 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10238 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10239 * 10240 * <li><i>minute</i> - 0 to 59 10241 * 10242 * <li><i>second</i> - 0 to 59 10243 * 10244 * <li><i>millisecond</i> - 0 to 999 10245 * 10246 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10247 * </ul> 10248 * 10249 * If the constructor is called with another Hebrew date instance instead of 10250 * a parameter block, the other instance acts as a parameter block and its 10251 * settings are copied into the current instance.<p> 10252 * 10253 * If the constructor is called with no arguments at all or if none of the 10254 * properties listed above are present, then the RD is calculate based on 10255 * the current date at the time of instantiation. <p> 10256 * 10257 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 10258 * specified in the params, it is assumed that they have the smallest possible 10259 * value in the range for the property (zero or one).<p> 10260 * 10261 * 10262 * @private 10263 * @constructor 10264 * @extends RataDie 10265 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew RD date 10266 */ 10267 var HebrewRataDie = function(params) { 10268 this.cal = params && params.cal || new HebrewCal(); 10269 this.rd = NaN; 10270 RataDie.call(this, params); 10271 }; 10272 10273 HebrewRataDie.prototype = new RataDie(); 10274 HebrewRataDie.prototype.parent = RataDie; 10275 HebrewRataDie.prototype.constructor = HebrewRataDie; 10276 10277 /** 10278 * The difference between a zero Julian day and the first day of the Hebrew 10279 * calendar: sunset on Monday, Tishri 1, 1 = September 7, 3760 BC Gregorian = JD 347997.25 10280 * @private 10281 * @type number 10282 */ 10283 HebrewRataDie.prototype.epoch = 347997.25; 10284 10285 /** 10286 * the cumulative lengths of each month for a non-leap year, without new years corrections 10287 * @private 10288 * @const 10289 * @type Array.<number> 10290 */ 10291 HebrewRataDie.cumMonthLengths = [ 10292 176, /* Nisan */ 10293 206, /* Iyyar */ 10294 235, /* Sivan */ 10295 265, /* Tammuz */ 10296 294, /* Av */ 10297 324, /* Elul */ 10298 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10299 30, /* Heshvan */ 10300 59, /* Kislev */ 10301 88, /* Teveth */ 10302 117, /* Shevat */ 10303 147 /* Adar I */ 10304 ]; 10305 10306 /** 10307 * the cumulative lengths of each month for a leap year, without new years corrections 10308 * @private 10309 * @const 10310 * @type Array.<number> 10311 */ 10312 HebrewRataDie.cumMonthLengthsLeap = [ 10313 206, /* Nisan */ 10314 236, /* Iyyar */ 10315 265, /* Sivan */ 10316 295, /* Tammuz */ 10317 324, /* Av */ 10318 354, /* Elul */ 10319 0, /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10320 30, /* Heshvan */ 10321 59, /* Kislev */ 10322 88, /* Teveth */ 10323 117, /* Shevat */ 10324 147, /* Adar I */ 10325 177 /* Adar II */ 10326 ]; 10327 10328 /** 10329 * Calculate the Rata Die (fixed day) number of the given date from the 10330 * date components. 10331 * 10332 * @private 10333 * @param {Object} date the date components to calculate the RD from 10334 */ 10335 HebrewRataDie.prototype._setDateComponents = function(date) { 10336 var elapsed = HebrewCal.elapsedDays(date.year); 10337 var days = elapsed + 10338 HebrewCal.newYearsCorrection(date.year, elapsed) + 10339 date.day - 1; 10340 var sum = 0, table; 10341 10342 //console.log("getRataDie: converting " + JSON.stringify(date)); 10343 //console.log("getRataDie: days is " + days); 10344 //console.log("getRataDie: new years correction is " + HebrewCal.newYearsCorrection(date.year, elapsed)); 10345 10346 table = this.cal.isLeapYear(date.year) ? 10347 HebrewRataDie.cumMonthLengthsLeap : 10348 HebrewRataDie.cumMonthLengths; 10349 sum = table[date.month-1]; 10350 10351 // gets cumulative without correction, so now add in the correction 10352 if ((date.month < 7 || date.month > 8) && HebrewCal.longHeshvan(date.year)) { 10353 sum++; 10354 } 10355 if ((date.month < 7 || date.month > 9) && HebrewCal.longKislev(date.year)) { 10356 sum++; 10357 } 10358 // console.log("getRataDie: cum days is now " + sum); 10359 10360 days += sum; 10361 10362 // the date starts at sunset, which we take as 18:00, so the hours from 10363 // midnight to 18:00 are on the current Gregorian day, and the hours from 10364 // 18:00 to midnight are on the previous Gregorian day. So to calculate the 10365 // number of hours into the current day that this time represents, we have 10366 // to count from 18:00 to midnight first, and add in 6 hours if the time is 10367 // less than 18:00 10368 var minute, second, millisecond; 10369 10370 if (typeof(date.parts) !== 'undefined') { 10371 // The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10372 var parts = parseInt(date.parts, 10); 10373 var seconds = parseInt(parts, 10) * 3.333333333333; 10374 minute = Math.floor(seconds / 60); 10375 seconds -= minute * 60; 10376 second = Math.floor(seconds); 10377 millisecond = (seconds - second); 10378 } else { 10379 minute = parseInt(date.minute, 10) || 0; 10380 second = parseInt(date.second, 10) || 0; 10381 millisecond = parseInt(date.millisecond, 10) || 0; 10382 } 10383 10384 var time; 10385 if (date.hour >= 18) { 10386 time = ((date.hour - 18 || 0) * 3600000 + 10387 (minute || 0) * 60000 + 10388 (second || 0) * 1000 + 10389 (millisecond || 0)) / 10390 86400000; 10391 } else { 10392 time = 0.25 + // 6 hours from 18:00 to midnight on the previous gregorian day 10393 ((date.hour || 0) * 3600000 + 10394 (minute || 0) * 60000 + 10395 (second || 0) * 1000 + 10396 (millisecond || 0)) / 10397 86400000; 10398 } 10399 10400 //console.log("getRataDie: rd is " + (days + time)); 10401 this.rd = days + time; 10402 }; 10403 10404 /** 10405 * Return the rd number of the particular day of the week on or before the 10406 * given rd. eg. The Sunday on or before the given rd. 10407 * @private 10408 * @param {number} rd the rata die date of the reference date 10409 * @param {number} dayOfWeek the day of the week that is being sought relative 10410 * to the current date 10411 * @return {number} the rd of the day of the week 10412 */ 10413 HebrewRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 10414 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek + 1, 7); 10415 }; 10416 10417 10418 10419 /*< HebrewDate.js */ 10420 /* 10421 * HebrewDate.js - Represent a date in the Hebrew calendar 10422 * 10423 * Copyright © 2012-2015, 2018, JEDLSoft 10424 * 10425 * Licensed under the Apache License, Version 2.0 (the "License"); 10426 * you may not use this file except in compliance with the License. 10427 * You may obtain a copy of the License at 10428 * 10429 * http://www.apache.org/licenses/LICENSE-2.0 10430 * 10431 * Unless required by applicable law or agreed to in writing, software 10432 * distributed under the License is distributed on an "AS IS" BASIS, 10433 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10434 * 10435 * See the License for the specific language governing permissions and 10436 * limitations under the License. 10437 */ 10438 10439 /* !depends 10440 ilib.js 10441 Locale.js 10442 LocaleInfo.js 10443 TimeZone.js 10444 IDate.js 10445 MathUtils.js 10446 HebrewCal.js 10447 HebrewRataDie.js 10448 */ 10449 10450 10451 10452 10453 /** 10454 * @class 10455 * Construct a new civil Hebrew date object. The constructor can be called 10456 * with a params object that can contain the following properties:<p> 10457 * 10458 * <ul> 10459 * <li><i>julianday</i> - the Julian Day to set into this date 10460 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 10461 * <li><i>month</i> - 1 to 12, where 1 means Nisan, 2 means Iyyar, etc. 10462 * <li><i>day</i> - 1 to 30 10463 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 10464 * is always done with an unambiguous 24 hour representation 10465 * <li><i>parts</i> - 0 to 1079. Specify the halaqim parts of an hour. Either specify 10466 * the parts or specify the minutes, seconds, and milliseconds, but not both. 10467 * <li><i>minute</i> - 0 to 59 10468 * <li><i>second</i> - 0 to 59 10469 * <li><i>millisecond</i> - 0 to 999 10470 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 10471 * of this julian date. The date/time is kept in the local time. The time zone 10472 * is used later if this date is formatted according to a different time zone and 10473 * the difference has to be calculated, or when the date format has a time zone 10474 * component in it. 10475 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 10476 * given, it can be inferred from this locale. For locales that span multiple 10477 * time zones, the one with the largest population is chosen as the one that 10478 * represents the locale. 10479 * 10480 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 10481 * </ul> 10482 * 10483 * If called with another Hebrew date argument, the date components of the given 10484 * date are copied into the current one.<p> 10485 * 10486 * If the constructor is called with no arguments at all or if none of the 10487 * properties listed above 10488 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 10489 * components are 10490 * filled in with the current date at the time of instantiation. Note that if 10491 * you do not give the time zone when defaulting to the current time and the 10492 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 10493 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 10494 * Mean Time").<p> 10495 * 10496 * 10497 * @constructor 10498 * @extends IDate 10499 * @param {Object=} params parameters that govern the settings and behaviour of this Hebrew date 10500 */ 10501 var HebrewDate = function(params) { 10502 this.cal = new HebrewCal(); 10503 10504 params = params || {}; 10505 10506 if (params.timezone) { 10507 this.timezone = params.timezone; 10508 } 10509 if (params.locale) { 10510 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 10511 } 10512 10513 if (!this.timezone) { 10514 if (this.locale) { 10515 new LocaleInfo(this.locale, { 10516 sync: params.sync, 10517 loadParams: params.loadParams, 10518 onLoad: ilib.bind(this, function(li) { 10519 this.li = li; 10520 this.timezone = li.getTimeZone(); 10521 this._init(params); 10522 }) 10523 }); 10524 } else { 10525 this.timezone = "local"; 10526 this._init(params); 10527 } 10528 } else { 10529 this._init(params); 10530 } 10531 }; 10532 10533 HebrewDate.prototype = new IDate({noinstance: true}); 10534 HebrewDate.prototype.parent = IDate; 10535 HebrewDate.prototype.constructor = HebrewDate; 10536 10537 /** 10538 * Initialize this date 10539 * @private 10540 */ 10541 HebrewDate.prototype._init = function (params) { 10542 if (params.year || params.month || params.day || params.hour || 10543 params.minute || params.second || params.millisecond || params.parts ) { 10544 /** 10545 * Year in the Hebrew calendar. 10546 * @type number 10547 */ 10548 this.year = parseInt(params.year, 10) || 0; 10549 10550 /** 10551 * The month number, ranging from 1 to 13. 10552 * @type number 10553 */ 10554 this.month = parseInt(params.month, 10) || 1; 10555 10556 /** 10557 * The day of the month. This ranges from 1 to 30. 10558 * @type number 10559 */ 10560 this.day = parseInt(params.day, 10) || 1; 10561 10562 /** 10563 * The hour of the day. This can be a number from 0 to 23, as times are 10564 * stored unambiguously in the 24-hour clock. 10565 * @type number 10566 */ 10567 this.hour = parseInt(params.hour, 10) || 0; 10568 10569 if (typeof(params.parts) !== 'undefined') { 10570 /** 10571 * The parts (halaqim) of the hour. This can be a number from 0 to 1079. 10572 * @type number 10573 */ 10574 this.parts = parseInt(params.parts, 10); 10575 var seconds = parseInt(params.parts, 10) * 3.333333333333; 10576 this.minute = Math.floor(seconds / 60); 10577 seconds -= this.minute * 60; 10578 this.second = Math.floor(seconds); 10579 this.millisecond = (seconds - this.second); 10580 } else { 10581 /** 10582 * The minute of the hours. Ranges from 0 to 59. 10583 * @type number 10584 */ 10585 this.minute = parseInt(params.minute, 10) || 0; 10586 10587 /** 10588 * The second of the minute. Ranges from 0 to 59. 10589 * @type number 10590 */ 10591 this.second = parseInt(params.second, 10) || 0; 10592 10593 /** 10594 * The millisecond of the second. Ranges from 0 to 999. 10595 * @type number 10596 */ 10597 this.millisecond = parseInt(params.millisecond, 10) || 0; 10598 } 10599 10600 /** 10601 * The day of the year. Ranges from 1 to 383. 10602 * @type number 10603 */ 10604 this.dayOfYear = parseInt(params.dayOfYear, 10); 10605 10606 if (typeof(params.dst) === 'boolean') { 10607 this.dst = params.dst; 10608 } 10609 10610 this.rd = this.newRd(this); 10611 10612 // add the time zone offset to the rd to convert to UTC 10613 new TimeZone({ 10614 id: this.timezone, 10615 sync: params.sync, 10616 loadParams: params.loadParams, 10617 onLoad: ilib.bind(this, function(tz) { 10618 this.tz = tz; 10619 // getOffsetMillis requires that this.year, this.rd, and this.dst 10620 // are set in order to figure out which time zone rules apply and 10621 // what the offset is at that point in the year 10622 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 10623 if (this.offset !== 0) { 10624 this.rd = this.newRd({ 10625 rd: this.rd.getRataDie() - this.offset 10626 }); 10627 } 10628 10629 this._init2(params); 10630 }) 10631 }); 10632 } else { 10633 this._init2(params); 10634 } 10635 }; 10636 10637 /** 10638 * Finish initializing this date 10639 * @private 10640 */ 10641 HebrewDate.prototype._init2 = function (params) { 10642 if (!this.rd) { 10643 this.rd = this.newRd(params); 10644 this._calcDateComponents(); 10645 } 10646 10647 if (typeof(params.onLoad) === "function") { 10648 params.onLoad(this); 10649 } 10650 }; 10651 10652 /** 10653 * the cumulative lengths of each month for a non-leap year, without new years corrections, 10654 * that can be used in reverse to map days to months 10655 * @private 10656 * @const 10657 * @type Array.<number> 10658 */ 10659 HebrewDate.cumMonthLengthsReverse = [ 10660 // [days, monthnumber], 10661 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10662 [30, 8], /* Heshvan */ 10663 [59, 9], /* Kislev */ 10664 [88, 10], /* Teveth */ 10665 [117, 11], /* Shevat */ 10666 [147, 12], /* Adar I */ 10667 [176, 1], /* Nisan */ 10668 [206, 2], /* Iyyar */ 10669 [235, 3], /* Sivan */ 10670 [265, 4], /* Tammuz */ 10671 [294, 5], /* Av */ 10672 [324, 6], /* Elul */ 10673 [354, 7] /* end of year sentinel value */ 10674 ]; 10675 10676 /** 10677 * the cumulative lengths of each month for a leap year, without new years corrections 10678 * that can be used in reverse to map days to months 10679 * 10680 * @private 10681 * @const 10682 * @type Array.<number> 10683 */ 10684 HebrewDate.cumMonthLengthsLeapReverse = [ 10685 // [days, monthnumber], 10686 [0, 7], /* Tishri - Jewish New Year (Rosh HaShanah) starts in month 7 */ 10687 [30, 8], /* Heshvan */ 10688 [59, 9], /* Kislev */ 10689 [88, 10], /* Teveth */ 10690 [117, 11], /* Shevat */ 10691 [147, 12], /* Adar I */ 10692 [177, 13], /* Adar II */ 10693 [206, 1], /* Nisan */ 10694 [236, 2], /* Iyyar */ 10695 [265, 3], /* Sivan */ 10696 [295, 4], /* Tammuz */ 10697 [324, 5], /* Av */ 10698 [354, 6], /* Elul */ 10699 [384, 7] /* end of year sentinel value */ 10700 ]; 10701 10702 /** 10703 * Number of days difference between RD 0 of the Hebrew calendar 10704 * (Jan 1, 1 Gregorian = JD 1721057.5) and RD 0 of the Hebrew calendar 10705 * (September 7, -3760 Gregorian = JD 347997.25) 10706 * @private 10707 * @const 10708 * @type number 10709 */ 10710 HebrewDate.GregorianDiff = 1373060.25; 10711 10712 /** 10713 * Return a new RD for this date type using the given params. 10714 * @private 10715 * @param {Object=} params the parameters used to create this rata die instance 10716 * @returns {RataDie} the new RD instance for the given params 10717 */ 10718 HebrewDate.prototype.newRd = function (params) { 10719 return new HebrewRataDie(params); 10720 }; 10721 10722 /** 10723 * Return the year for the given RD 10724 * @protected 10725 * @param {number} rd RD to calculate from 10726 * @returns {number} the year for the RD 10727 */ 10728 HebrewDate.prototype._calcYear = function(rd) { 10729 var year, approximation, nextNewYear; 10730 10731 // divide by the average number of days per year in the Hebrew calendar 10732 // to approximate the year, then tweak it to get the real year 10733 approximation = Math.floor(rd / 365.246822206) + 1; 10734 10735 // console.log("HebrewDate._calcYear: approx is " + approximation); 10736 10737 // search forward from approximation-1 for the year that actually contains this rd 10738 year = approximation; 10739 nextNewYear = HebrewCal.newYear(year); 10740 while (rd >= nextNewYear) { 10741 year++; 10742 nextNewYear = HebrewCal.newYear(year); 10743 } 10744 return year - 1; 10745 }; 10746 10747 /** 10748 * Calculate date components for the given RD date. 10749 * @protected 10750 */ 10751 HebrewDate.prototype._calcDateComponents = function () { 10752 var remainder, 10753 i, 10754 table, 10755 target, 10756 rd = this.rd.getRataDie(); 10757 10758 // console.log("HebrewDate.calcComponents: calculating for rd " + rd); 10759 10760 if (typeof(this.offset) === "undefined") { 10761 this.year = this._calcYear(rd); 10762 10763 // now offset the RD by the time zone, then recalculate in case we were 10764 // near the year boundary 10765 if (!this.tz) { 10766 this.tz = new TimeZone({id: this.timezone}); 10767 } 10768 this.offset = this.tz.getOffsetMillis(this) / 86400000; 10769 } 10770 10771 if (this.offset !== 0) { 10772 rd += this.offset; 10773 this.year = this._calcYear(rd); 10774 } 10775 10776 // console.log("HebrewDate.calcComponents: year is " + this.year + " with starting rd " + thisNewYear); 10777 10778 remainder = rd - HebrewCal.newYear(this.year); 10779 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 10780 10781 // take out new years corrections so we get the right month when we look it up in the table 10782 if (remainder >= 59) { 10783 if (remainder >= 88) { 10784 if (HebrewCal.longKislev(this.year)) { 10785 remainder--; 10786 } 10787 } 10788 if (HebrewCal.longHeshvan(this.year)) { 10789 remainder--; 10790 } 10791 } 10792 10793 // console.log("HebrewDate.calcComponents: after new years corrections, remainder is " + remainder); 10794 10795 table = this.cal.isLeapYear(this.year) ? 10796 HebrewDate.cumMonthLengthsLeapReverse : 10797 HebrewDate.cumMonthLengthsReverse; 10798 10799 i = 0; 10800 target = Math.floor(remainder); 10801 while (i+1 < table.length && target >= table[i+1][0]) { 10802 i++; 10803 } 10804 10805 this.month = table[i][1]; 10806 // console.log("HebrewDate.calcComponents: remainder is " + remainder); 10807 remainder -= table[i][0]; 10808 10809 // console.log("HebrewDate.calcComponents: month is " + this.month + " and remainder is " + remainder); 10810 10811 this.day = Math.floor(remainder); 10812 remainder -= this.day; 10813 this.day++; // days are 1-based 10814 10815 // console.log("HebrewDate.calcComponents: day is " + this.day + " and remainder is " + remainder); 10816 10817 // now convert to milliseconds for the rest of the calculation 10818 remainder = Math.round(remainder * 86400000); 10819 10820 this.hour = Math.floor(remainder/3600000); 10821 remainder -= this.hour * 3600000; 10822 10823 // the hours from 0 to 6 are actually 18:00 to midnight of the previous 10824 // gregorian day, so we have to adjust for that 10825 if (this.hour >= 6) { 10826 this.hour -= 6; 10827 } else { 10828 this.hour += 18; 10829 } 10830 10831 this.minute = Math.floor(remainder/60000); 10832 remainder -= this.minute * 60000; 10833 10834 this.second = Math.floor(remainder/1000); 10835 remainder -= this.second * 1000; 10836 10837 this.millisecond = Math.floor(remainder); 10838 }; 10839 10840 /** 10841 * Return the day of the week of this date. The day of the week is encoded 10842 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 10843 * 10844 * @return {number} the day of the week 10845 */ 10846 HebrewDate.prototype.getDayOfWeek = function() { 10847 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 10848 return MathUtils.mod(rd+1, 7); 10849 }; 10850 10851 /** 10852 * Get the Halaqim (parts) of an hour. There are 1080 parts in an hour, which means 10853 * each part is 3.33333333 seconds long. This means the number returned may not 10854 * be an integer. 10855 * 10856 * @return {number} the halaqim parts of the current hour 10857 */ 10858 HebrewDate.prototype.getHalaqim = function() { 10859 if (this.parts < 0) { 10860 // convert to ms first, then to parts 10861 var h = this.minute * 60000 + this.second * 1000 + this.millisecond; 10862 this.parts = (h * 0.0003); 10863 } 10864 return this.parts; 10865 }; 10866 10867 /** 10868 * Return the rd number of the first Sunday of the given ISO year. 10869 * @protected 10870 * @return the rd of the first Sunday of the ISO year 10871 */ 10872 HebrewDate.prototype.firstSunday = function (year) { 10873 var tishri1 = this.newRd({ 10874 year: year, 10875 month: 7, 10876 day: 1, 10877 hour: 18, 10878 minute: 0, 10879 second: 0, 10880 millisecond: 0, 10881 cal: this.cal 10882 }); 10883 var firstThu = this.newRd({ 10884 rd: tishri1.onOrAfter(4), 10885 cal: this.cal 10886 }); 10887 return firstThu.before(0); 10888 }; 10889 10890 /** 10891 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 10892 * 385, regardless of months or weeks, etc. That is, Tishri 1st is day 1, and 10893 * Elul 29 is 385 for a leap year with a long Heshvan and long Kislev. 10894 * @return {number} the ordinal day of the year 10895 */ 10896 HebrewDate.prototype.getDayOfYear = function() { 10897 var table = this.cal.isLeapYear(this.year) ? 10898 HebrewRataDie.cumMonthLengthsLeap : 10899 HebrewRataDie.cumMonthLengths; 10900 var days = table[this.month-1]; 10901 if ((this.month < 7 || this.month > 8) && HebrewCal.longHeshvan(this.year)) { 10902 days++; 10903 } 10904 if ((this.month < 7 || this.month > 9) && HebrewCal.longKislev(this.year)) { 10905 days++; 10906 } 10907 10908 return days + this.day; 10909 }; 10910 10911 /** 10912 * Return the ordinal number of the week within the month. The first week of a month is 10913 * the first one that contains 4 or more days in that month. If any days precede this 10914 * first week, they are marked as being in week 0. This function returns values from 0 10915 * through 6.<p> 10916 * 10917 * The locale is a required parameter because different locales that use the same 10918 * Hebrew calendar consider different days of the week to be the beginning of 10919 * the week. This can affect the week of the month in which some days are located. 10920 * 10921 * @param {Locale|string} locale the locale or locale spec to use when figuring out 10922 * the first day of the week 10923 * @return {number} the ordinal number of the week within the current month 10924 */ 10925 HebrewDate.prototype.getWeekOfMonth = function(locale) { 10926 var li = new LocaleInfo(locale), 10927 first = this.newRd({ 10928 year: this.year, 10929 month: this.month, 10930 day: 1, 10931 hour: 18, 10932 minute: 0, 10933 second: 0, 10934 millisecond: 0 10935 }), 10936 rd = this.rd.getRataDie(), 10937 weekStart = first.onOrAfter(li.getFirstDayOfWeek()); 10938 10939 if (weekStart - first.getRataDie() > 3) { 10940 // if the first week has 4 or more days in it of the current month, then consider 10941 // that week 1. Otherwise, it is week 0. To make it week 1, move the week start 10942 // one week earlier. 10943 weekStart -= 7; 10944 } 10945 return (rd < weekStart) ? 0 : Math.floor((rd - weekStart) / 7) + 1; 10946 }; 10947 10948 /** 10949 * Return the era for this date as a number. The value for the era for Hebrew 10950 * calendars is -1 for "before the Hebrew era" and 1 for "the Hebrew era". 10951 * Hebrew era dates are any date after Tishri 1, 1, which is the same as 10952 * September 7, 3760 BC in the Gregorian calendar. 10953 * 10954 * @return {number} 1 if this date is in the Hebrew era, -1 if it is before the 10955 * Hebrew era 10956 */ 10957 HebrewDate.prototype.getEra = function() { 10958 return (this.year < 1) ? -1 : 1; 10959 }; 10960 10961 /** 10962 * Return the name of the calendar that governs this date. 10963 * 10964 * @return {string} a string giving the name of the calendar 10965 */ 10966 HebrewDate.prototype.getCalendar = function() { 10967 return "hebrew"; 10968 }; 10969 10970 // register with the factory method 10971 IDate._constructors["hebrew"] = HebrewDate; 10972 10973 10974 10975 /*< IslamicCal.js */ 10976 /* 10977 * islamic.js - Represent a Islamic calendar object. 10978 * 10979 * Copyright © 2012-2015,2018, JEDLSoft 10980 * 10981 * Licensed under the Apache License, Version 2.0 (the "License"); 10982 * you may not use this file except in compliance with the License. 10983 * You may obtain a copy of the License at 10984 * 10985 * http://www.apache.org/licenses/LICENSE-2.0 10986 * 10987 * Unless required by applicable law or agreed to in writing, software 10988 * distributed under the License is distributed on an "AS IS" BASIS, 10989 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 10990 * 10991 * See the License for the specific language governing permissions and 10992 * limitations under the License. 10993 */ 10994 10995 10996 /* !depends 10997 Calendar.js 10998 MathUtils.js 10999 */ 11000 11001 11002 /** 11003 * @class 11004 * Construct a new Islamic calendar object. This class encodes information about 11005 * the civil Islamic calendar. The civil Islamic calendar is a tabular islamic 11006 * calendar where the dates are calculated by arithmetic rules. This differs from 11007 * the religious Islamic calendar which is used to mark the beginning of particular 11008 * holidays. The religious calendar depends on the first sighting of the new 11009 * crescent moon to determine the first day of the new month. Because humans and 11010 * weather are both involved, the actual time of sighting varies, so it is not 11011 * really possible to precalculate the religious calendar. Certain groups, such 11012 * as the Islamic Society of North America, decreed in in 2007 that they will use 11013 * a calendar based on calculations rather than observations to determine the 11014 * beginning of lunar months, and therefore the dates of holidays.<p> 11015 * 11016 * @param {Object=} options Options governing the construction of this instance 11017 * @constructor 11018 * @extends Calendar 11019 */ 11020 var IslamicCal = function(options) { 11021 this.type = "islamic"; 11022 11023 if (options && typeof(options.onLoad) === "function") { 11024 options.onLoad(this); 11025 } 11026 }; 11027 11028 /** 11029 * the lengths of each month 11030 * @private 11031 * @const 11032 * @type Array.<number> 11033 */ 11034 IslamicCal.monthLengths = [ 11035 30, /* Muharram */ 11036 29, /* Saffar */ 11037 30, /* Rabi'I */ 11038 29, /* Rabi'II */ 11039 30, /* Jumada I */ 11040 29, /* Jumada II */ 11041 30, /* Rajab */ 11042 29, /* Sha'ban */ 11043 30, /* Ramadan */ 11044 29, /* Shawwal */ 11045 30, /* Dhu al-Qa'da */ 11046 29 /* Dhu al-Hijja */ 11047 ]; 11048 11049 11050 /** 11051 * Return the number of months in the given year. The number of months in a year varies 11052 * for luni-solar calendars because in some years, an extra month is needed to extend the 11053 * days in a year to an entire solar year. The month is represented as a 1-based number 11054 * where 1=first month, 2=second month, etc. 11055 * 11056 * @param {number} year a year for which the number of months is sought 11057 */ 11058 IslamicCal.prototype.getNumMonths = function(year) { 11059 return 12; 11060 }; 11061 11062 /** 11063 * Return the number of days in a particular month in a particular year. This function 11064 * can return a different number for a month depending on the year because of things 11065 * like leap years. 11066 * 11067 * @param {number} month the month for which the length is sought 11068 * @param {number} year the year within which that month can be found 11069 * @return {number} the number of days within the given month in the given year 11070 */ 11071 IslamicCal.prototype.getMonLength = function(month, year) { 11072 if (month !== 12) { 11073 return IslamicCal.monthLengths[month-1]; 11074 } else { 11075 return this.isLeapYear(year) ? 30 : 29; 11076 } 11077 }; 11078 11079 /** 11080 * Return true if the given year is a leap year in the Islamic calendar. 11081 * The year parameter may be given as a number, or as a IslamicDate object. 11082 * @param {number} year the year for which the leap year information is being sought 11083 * @return {boolean} true if the given year is a leap year 11084 */ 11085 IslamicCal.prototype.isLeapYear = function(year) { 11086 return (MathUtils.mod((14 + 11 * year), 30) < 11); 11087 }; 11088 11089 /** 11090 * Return the type of this calendar. 11091 * 11092 * @return {string} the name of the type of this calendar 11093 */ 11094 IslamicCal.prototype.getType = function() { 11095 return this.type; 11096 }; 11097 11098 11099 /*register this calendar for the factory method */ 11100 Calendar._constructors["islamic"] = IslamicCal; 11101 11102 11103 11104 /*< SearchUtils.js */ 11105 /* 11106 * SearchUtils.js - Misc search utility routines 11107 * 11108 * Copyright © 2013-2015, JEDLSoft 11109 * 11110 * Licensed under the Apache License, Version 2.0 (the "License"); 11111 * you may not use this file except in compliance with the License. 11112 * You may obtain a copy of the License at 11113 * 11114 * http://www.apache.org/licenses/LICENSE-2.0 11115 * 11116 * Unless required by applicable law or agreed to in writing, software 11117 * distributed under the License is distributed on an "AS IS" BASIS, 11118 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11119 * 11120 * See the License for the specific language governing permissions and 11121 * limitations under the License. 11122 */ 11123 11124 var SearchUtils = {}; 11125 11126 /** 11127 * Binary search a sorted array for a particular target value. 11128 * If the exact value is not found, it returns the index of the smallest 11129 * entry that is greater than the given target value.<p> 11130 * 11131 * The comparator 11132 * parameter is a function that knows how to compare elements of the 11133 * array and the target. The function should return a value greater than 0 11134 * if the array element is greater than the target, a value less than 0 if 11135 * the array element is less than the target, and 0 if the array element 11136 * and the target are equivalent.<p> 11137 * 11138 * If the comparator function is not specified, this function assumes 11139 * the array and the target are numeric values and should be compared 11140 * as such.<p> 11141 * 11142 * 11143 * @static 11144 * @param {*} target element being sought 11145 * @param {Array} arr the array being searched 11146 * @param {?function(*,*)=} comparator a comparator that is appropriate for comparing two entries 11147 * in the array 11148 * @return the index of the array into which the value would fit if 11149 * inserted, or -1 if given array is not an array or the target is not 11150 * a number 11151 */ 11152 SearchUtils.bsearch = function(target, arr, comparator) { 11153 if (typeof(arr) === 'undefined' || !arr || typeof(target) === 'undefined') { 11154 return -1; 11155 } 11156 11157 var high = arr.length - 1, 11158 low = 0, 11159 mid = 0, 11160 value, 11161 cmp = comparator || SearchUtils.bsearch.numbers; 11162 11163 while (low <= high) { 11164 mid = Math.floor((high+low)/2); 11165 value = cmp(arr[mid], target); 11166 if (value > 0) { 11167 high = mid - 1; 11168 } else if (value < 0) { 11169 low = mid + 1; 11170 } else { 11171 return mid; 11172 } 11173 } 11174 11175 return low; 11176 }; 11177 11178 /** 11179 * Returns whether or not the given element is greater than, less than, 11180 * or equal to the given target.<p> 11181 * 11182 * @private 11183 * @static 11184 * @param {number} element the element being tested 11185 * @param {number} target the target being sought 11186 */ 11187 SearchUtils.bsearch.numbers = function(element, target) { 11188 return element - target; 11189 }; 11190 11191 /** 11192 * Do a bisection search of a function for a particular target value.<p> 11193 * 11194 * The function to search is a function that takes a numeric parameter, 11195 * does calculations, and returns gives a numeric result. The 11196 * function should should be smooth and not have any discontinuities 11197 * between the low and high values of the parameter. 11198 * 11199 * 11200 * @static 11201 * @param {number} target value being sought 11202 * @param {number} low the lower bounds to start searching 11203 * @param {number} high the upper bounds to start searching 11204 * @param {number} precision minimum precision to support. Use 0 if you want to use the default. 11205 * @param {?function(number)=} func function to search 11206 * @return an approximation of the input value to the function that gives the desired 11207 * target output value, correct to within the error range of Javascript floating point 11208 * arithmetic, or NaN if there was some error 11209 */ 11210 SearchUtils.bisectionSearch = function(target, low, high, precision, func) { 11211 if (typeof(target) !== 'number' || 11212 typeof(low) !== 'number' || 11213 typeof(high) !== 'number' || 11214 typeof(func) !== 'function') { 11215 return NaN; 11216 } 11217 11218 var mid = 0, 11219 value, 11220 pre = precision > 0 ? precision : 1e-13; 11221 11222 do { 11223 mid = (high+low)/2; 11224 value = func(mid); 11225 if (value > target) { 11226 high = mid; 11227 } else if (value < target) { 11228 low = mid; 11229 } 11230 } while (high - low > pre); 11231 11232 return mid; 11233 }; 11234 11235 11236 11237 /*< IslamicRataDie.js */ 11238 /* 11239 * IslamicRataDie.js - Represent an RD date in the Islamic calendar 11240 * 11241 * Copyright © 2012-2015, JEDLSoft 11242 * 11243 * Licensed under the Apache License, Version 2.0 (the "License"); 11244 * you may not use this file except in compliance with the License. 11245 * You may obtain a copy of the License at 11246 * 11247 * http://www.apache.org/licenses/LICENSE-2.0 11248 * 11249 * Unless required by applicable law or agreed to in writing, software 11250 * distributed under the License is distributed on an "AS IS" BASIS, 11251 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11252 * 11253 * See the License for the specific language governing permissions and 11254 * limitations under the License. 11255 */ 11256 11257 /* !depends 11258 IslamicCal.js 11259 RataDie.js 11260 */ 11261 11262 11263 /** 11264 * @class 11265 * Construct a new Islamic RD date number object. The constructor parameters can 11266 * contain any of the following properties: 11267 * 11268 * <ul> 11269 * <li><i>unixtime<i> - sets the time of this instance according to the given 11270 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 11271 * 11272 * <li><i>julianday</i> - sets the time of this instance according to the given 11273 * Julian Day instance or the Julian Day given as a float 11274 * 11275 * <li><i>year</i> - any integer, including 0 11276 * 11277 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11278 * 11279 * <li><i>day</i> - 1 to 31 11280 * 11281 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11282 * is always done with an unambiguous 24 hour representation 11283 * 11284 * <li><i>minute</i> - 0 to 59 11285 * 11286 * <li><i>second</i> - 0 to 59 11287 * 11288 * <li><i>millisecond</i> - 0 to 999 11289 * 11290 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11291 * </ul> 11292 * 11293 * If the constructor is called with another Islamic date instance instead of 11294 * a parameter block, the other instance acts as a parameter block and its 11295 * settings are copied into the current instance.<p> 11296 * 11297 * If the constructor is called with no arguments at all or if none of the 11298 * properties listed above are present, then the RD is calculate based on 11299 * the current date at the time of instantiation. <p> 11300 * 11301 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 11302 * specified in the params, it is assumed that they have the smallest possible 11303 * value in the range for the property (zero or one).<p> 11304 * 11305 * 11306 * @private 11307 * @constructor 11308 * @extends RataDie 11309 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic RD date 11310 */ 11311 var IslamicRataDie = function(params) { 11312 this.cal = params && params.cal || new IslamicCal(); 11313 this.rd = NaN; 11314 RataDie.call(this, params); 11315 }; 11316 11317 IslamicRataDie.prototype = new RataDie(); 11318 IslamicRataDie.prototype.parent = RataDie; 11319 IslamicRataDie.prototype.constructor = IslamicRataDie; 11320 11321 /** 11322 * The difference between a zero Julian day and the first Islamic date 11323 * of Friday, July 16, 622 CE Julian. 11324 * @private 11325 * @type number 11326 */ 11327 IslamicRataDie.prototype.epoch = 1948439.5; 11328 11329 /** 11330 * Calculate the Rata Die (fixed day) number of the given date from the 11331 * date components. 11332 * 11333 * @protected 11334 * @param {Object} date the date components to calculate the RD from 11335 */ 11336 IslamicRataDie.prototype._setDateComponents = function(date) { 11337 var days = (date.year - 1) * 354 + 11338 Math.ceil(29.5 * (date.month - 1)) + 11339 date.day + 11340 Math.floor((3 + 11 * date.year) / 30) - 1; 11341 var time = (date.hour * 3600000 + 11342 date.minute * 60000 + 11343 date.second * 1000 + 11344 date.millisecond) / 11345 86400000; 11346 11347 //console.log("getRataDie: converting " + JSON.stringify(date)); 11348 //console.log("getRataDie: days is " + days); 11349 //console.log("getRataDie: time is " + time); 11350 //console.log("getRataDie: rd is " + (days + time)); 11351 11352 this.rd = days + time; 11353 }; 11354 11355 11356 /*< IslamicDate.js */ 11357 /* 11358 * islamicDate.js - Represent a date in the Islamic calendar 11359 * 11360 * Copyright © 2012-2015, JEDLSoft 11361 * 11362 * Licensed under the Apache License, Version 2.0 (the "License"); 11363 * you may not use this file except in compliance with the License. 11364 * You may obtain a copy of the License at 11365 * 11366 * http://www.apache.org/licenses/LICENSE-2.0 11367 * 11368 * Unless required by applicable law or agreed to in writing, software 11369 * distributed under the License is distributed on an "AS IS" BASIS, 11370 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11371 * 11372 * See the License for the specific language governing permissions and 11373 * limitations under the License. 11374 */ 11375 11376 /* !depends 11377 ilib.js 11378 Locale.js 11379 LocaleInfo.js 11380 TimeZone.js 11381 IDate.js 11382 MathUtils.js 11383 SearchUtils.js 11384 IslamicCal.js 11385 IslamicRataDie.js 11386 */ 11387 11388 11389 11390 11391 /** 11392 * @class 11393 * Construct a new civil Islamic date object. The constructor can be called 11394 * with a params object that can contain the following properties:<p> 11395 * 11396 * <ul> 11397 * <li><i>julianday</i> - the Julian Day to set into this date 11398 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero year 11399 * <li><i>month</i> - 1 to 12, where 1 means Muharram, 2 means Saffar, etc. 11400 * <li><i>day</i> - 1 to 30 11401 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11402 * is always done with an unambiguous 24 hour representation 11403 * <li><i>minute</i> - 0 to 59 11404 * <li><i>second</i> - 0 to 59 11405 * <li><i>millisecond</i> - 0 to 999 11406 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 11407 * of this julian date. The date/time is kept in the local time. The time zone 11408 * is used later if this date is formatted according to a different time zone and 11409 * the difference has to be calculated, or when the date format has a time zone 11410 * component in it. 11411 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 11412 * given, it can be inferred from this locale. For locales that span multiple 11413 * time zones, the one with the largest population is chosen as the one that 11414 * represents the locale. 11415 * 11416 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11417 * </ul> 11418 * 11419 * If called with another Islamic date argument, the date components of the given 11420 * date are copied into the current one.<p> 11421 * 11422 * If the constructor is called with no arguments at all or if none of the 11423 * properties listed above 11424 * from <i>julianday</i> through <i>millisecond</i> are present, then the date 11425 * components are 11426 * filled in with the current date at the time of instantiation. Note that if 11427 * you do not give the time zone when defaulting to the current time and the 11428 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 11429 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 11430 * Mean Time").<p> 11431 * 11432 * 11433 * @constructor 11434 * @extends IDate 11435 * @param {Object=} params parameters that govern the settings and behaviour of this Islamic date 11436 */ 11437 var IslamicDate = function(params) { 11438 this.cal = new IslamicCal(); 11439 11440 params = params || {}; 11441 11442 if (params.timezone) { 11443 this.timezone = params.timezone; 11444 } 11445 if (params.locale) { 11446 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 11447 } 11448 11449 if (!this.timezone) { 11450 if (this.locale) { 11451 new LocaleInfo(this.locale, { 11452 sync: params.sync, 11453 loadParams: params.loadParams, 11454 onLoad: ilib.bind(this, function(li) { 11455 this.li = li; 11456 this.timezone = li.getTimeZone(); 11457 this._init(params); 11458 }) 11459 }); 11460 } else { 11461 this.timezone = "local"; 11462 this._init(params); 11463 } 11464 } else { 11465 this._init(params); 11466 } 11467 }; 11468 11469 IslamicDate.prototype = new IDate({noinstance: true}); 11470 IslamicDate.prototype.parent = IDate; 11471 IslamicDate.prototype.constructor = IslamicDate; 11472 11473 /** 11474 * Initialize the date 11475 * @private 11476 */ 11477 IslamicDate.prototype._init = function (params) { 11478 if (params.year || params.month || params.day || params.hour || 11479 params.minute || params.second || params.millisecond ) { 11480 /** 11481 * Year in the Islamic calendar. 11482 * @type number 11483 */ 11484 this.year = parseInt(params.year, 10) || 0; 11485 11486 /** 11487 * The month number, ranging from 1 to 12 (December). 11488 * @type number 11489 */ 11490 this.month = parseInt(params.month, 10) || 1; 11491 11492 /** 11493 * The day of the month. This ranges from 1 to 30. 11494 * @type number 11495 */ 11496 this.day = parseInt(params.day, 10) || 1; 11497 11498 /** 11499 * The hour of the day. This can be a number from 0 to 23, as times are 11500 * stored unambiguously in the 24-hour clock. 11501 * @type number 11502 */ 11503 this.hour = parseInt(params.hour, 10) || 0; 11504 11505 /** 11506 * The minute of the hours. Ranges from 0 to 59. 11507 * @type number 11508 */ 11509 this.minute = parseInt(params.minute, 10) || 0; 11510 11511 /** 11512 * The second of the minute. Ranges from 0 to 59. 11513 * @type number 11514 */ 11515 this.second = parseInt(params.second, 10) || 0; 11516 11517 /** 11518 * The millisecond of the second. Ranges from 0 to 999. 11519 * @type number 11520 */ 11521 this.millisecond = parseInt(params.millisecond, 10) || 0; 11522 11523 /** 11524 * The day of the year. Ranges from 1 to 355. 11525 * @type number 11526 */ 11527 this.dayOfYear = parseInt(params.dayOfYear, 10); 11528 11529 if (typeof(params.dst) === 'boolean') { 11530 this.dst = params.dst; 11531 } 11532 11533 this.rd = this.newRd(this); 11534 11535 new TimeZone({ 11536 id: this.timezone, 11537 sync: params.sync, 11538 loadParams: params.loadParams, 11539 onLoad: ilib.bind(this, function(tz) { 11540 this.tz = tz; 11541 // add the time zone offset to the rd to convert to UTC 11542 // getOffsetMillis requires that this.year, this.rd, and this.dst 11543 // are set in order to figure out which time zone rules apply and 11544 // what the offset is at that point in the year 11545 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 11546 if (this.offset !== 0) { 11547 this.rd = this.newRd({ 11548 rd: this.rd.getRataDie() - this.offset 11549 }); 11550 } 11551 this._init2(params); 11552 }) 11553 }); 11554 } else { 11555 this._init2(params); 11556 } 11557 }; 11558 11559 /** 11560 * @private 11561 * Finish initializing this date object 11562 */ 11563 IslamicDate.prototype._init2 = function (params) { 11564 if (!this.rd) { 11565 this.rd = this.newRd(params); 11566 this._calcDateComponents(); 11567 } 11568 11569 if (typeof(params.onLoad) === "function") { 11570 params.onLoad(this); 11571 } 11572 }; 11573 11574 /** 11575 * the cumulative lengths of each month, for a non-leap year 11576 * @private 11577 * @const 11578 * @type Array.<number> 11579 */ 11580 IslamicDate.cumMonthLengths = [ 11581 0, /* Muharram */ 11582 30, /* Saffar */ 11583 59, /* Rabi'I */ 11584 89, /* Rabi'II */ 11585 118, /* Jumada I */ 11586 148, /* Jumada II */ 11587 177, /* Rajab */ 11588 207, /* Sha'ban */ 11589 236, /* Ramadan */ 11590 266, /* Shawwal */ 11591 295, /* Dhu al-Qa'da */ 11592 325, /* Dhu al-Hijja */ 11593 354 11594 ]; 11595 11596 /** 11597 * Number of days difference between RD 0 of the Gregorian calendar and 11598 * RD 0 of the Islamic calendar. 11599 * @private 11600 * @const 11601 * @type number 11602 */ 11603 IslamicDate.GregorianDiff = 227015; 11604 11605 /** 11606 * Return a new RD for this date type using the given params. 11607 * @protected 11608 * @param {Object=} params the parameters used to create this rata die instance 11609 * @returns {RataDie} the new RD instance for the given params 11610 */ 11611 IslamicDate.prototype.newRd = function (params) { 11612 return new IslamicRataDie(params); 11613 }; 11614 11615 /** 11616 * Return the year for the given RD 11617 * @protected 11618 * @param {number} rd RD to calculate from 11619 * @returns {number} the year for the RD 11620 */ 11621 IslamicDate.prototype._calcYear = function(rd) { 11622 return Math.floor((30 * rd + 10646) / 10631); 11623 }; 11624 11625 /** 11626 * Calculate date components for the given RD date. 11627 * @protected 11628 */ 11629 IslamicDate.prototype._calcDateComponents = function () { 11630 var remainder, 11631 rd = this.rd.getRataDie(); 11632 11633 this.year = this._calcYear(rd); 11634 11635 if (typeof(this.offset) === "undefined") { 11636 this.year = this._calcYear(rd); 11637 11638 // now offset the RD by the time zone, then recalculate in case we were 11639 // near the year boundary 11640 if (!this.tz) { 11641 this.tz = new TimeZone({id: this.timezone}); 11642 } 11643 this.offset = this.tz.getOffsetMillis(this) / 86400000; 11644 } 11645 11646 if (this.offset !== 0) { 11647 rd += this.offset; 11648 this.year = this._calcYear(rd); 11649 } 11650 11651 //console.log("IslamicDate.calcComponent: calculating for rd " + rd); 11652 //console.log("IslamicDate.calcComponent: year is " + ret.year); 11653 var yearStart = this.newRd({ 11654 year: this.year, 11655 month: 1, 11656 day: 1, 11657 hour: 0, 11658 minute: 0, 11659 second: 0, 11660 millisecond: 0 11661 }); 11662 remainder = rd - yearStart.getRataDie() + 1; 11663 11664 this.dayOfYear = remainder; 11665 11666 //console.log("IslamicDate.calcComponent: remainder is " + remainder); 11667 11668 this.month = SearchUtils.bsearch(remainder, IslamicDate.cumMonthLengths); 11669 remainder -= IslamicDate.cumMonthLengths[this.month-1]; 11670 11671 //console.log("IslamicDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 11672 11673 this.day = Math.floor(remainder); 11674 remainder -= this.day; 11675 11676 //console.log("IslamicDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 11677 11678 // now convert to milliseconds for the rest of the calculation 11679 remainder = Math.round(remainder * 86400000); 11680 11681 this.hour = Math.floor(remainder/3600000); 11682 remainder -= this.hour * 3600000; 11683 11684 this.minute = Math.floor(remainder/60000); 11685 remainder -= this.minute * 60000; 11686 11687 this.second = Math.floor(remainder/1000); 11688 remainder -= this.second * 1000; 11689 11690 this.millisecond = remainder; 11691 }; 11692 11693 /** 11694 * Return the day of the week of this date. The day of the week is encoded 11695 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 11696 * 11697 * @return {number} the day of the week 11698 */ 11699 IslamicDate.prototype.getDayOfWeek = function() { 11700 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 11701 return MathUtils.mod(rd-2, 7); 11702 }; 11703 11704 /** 11705 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 11706 * 354 or 355, regardless of months or weeks, etc. That is, Muharran 1st is day 1, and 11707 * Dhu al-Hijja 29 is 354. 11708 * @return {number} the ordinal day of the year 11709 */ 11710 IslamicDate.prototype.getDayOfYear = function() { 11711 return IslamicDate.cumMonthLengths[this.month-1] + this.day; 11712 }; 11713 11714 /** 11715 * Return the era for this date as a number. The value for the era for Islamic 11716 * calendars is -1 for "before the Islamic era" and 1 for "the Islamic era". 11717 * Islamic era dates are any date after Muharran 1, 1, which is the same as 11718 * July 16, 622 CE in the Gregorian calendar. 11719 * 11720 * @return {number} 1 if this date is in the common era, -1 if it is before the 11721 * common era 11722 */ 11723 IslamicDate.prototype.getEra = function() { 11724 return (this.year < 1) ? -1 : 1; 11725 }; 11726 11727 /** 11728 * Return the name of the calendar that governs this date. 11729 * 11730 * @return {string} a string giving the name of the calendar 11731 */ 11732 IslamicDate.prototype.getCalendar = function() { 11733 return "islamic"; 11734 }; 11735 11736 //register with the factory method 11737 IDate._constructors["islamic"] = IslamicDate; 11738 11739 11740 /*< JulianCal.js */ 11741 /* 11742 * julian.js - Represent a Julian calendar object. 11743 * 11744 * Copyright © 2012-2015,2018, JEDLSoft 11745 * 11746 * Licensed under the Apache License, Version 2.0 (the "License"); 11747 * you may not use this file except in compliance with the License. 11748 * You may obtain a copy of the License at 11749 * 11750 * http://www.apache.org/licenses/LICENSE-2.0 11751 * 11752 * Unless required by applicable law or agreed to in writing, software 11753 * distributed under the License is distributed on an "AS IS" BASIS, 11754 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11755 * 11756 * See the License for the specific language governing permissions and 11757 * limitations under the License. 11758 */ 11759 11760 11761 /* !depends Calendar.js MathUtils.js */ 11762 11763 11764 /** 11765 * @class 11766 * Construct a new Julian calendar object. This class encodes information about 11767 * a Julian calendar.<p> 11768 * 11769 * @param {Object=} options Options governing the construction of this instance 11770 * @constructor 11771 * @extends Calendar 11772 */ 11773 var JulianCal = function(options) { 11774 this.type = "julian"; 11775 11776 if (options && typeof(options.onLoad) === "function") { 11777 options.onLoad(this); 11778 } 11779 }; 11780 11781 /* the lengths of each month */ 11782 JulianCal.monthLengths = [ 11783 31, /* Jan */ 11784 28, /* Feb */ 11785 31, /* Mar */ 11786 30, /* Apr */ 11787 31, /* May */ 11788 30, /* Jun */ 11789 31, /* Jul */ 11790 31, /* Aug */ 11791 30, /* Sep */ 11792 31, /* Oct */ 11793 30, /* Nov */ 11794 31 /* Dec */ 11795 ]; 11796 11797 /** 11798 * the cumulative lengths of each month, for a non-leap year 11799 * @private 11800 * @const 11801 * @type Array.<number> 11802 */ 11803 JulianCal.cumMonthLengths = [ 11804 0, /* Jan */ 11805 31, /* Feb */ 11806 59, /* Mar */ 11807 90, /* Apr */ 11808 120, /* May */ 11809 151, /* Jun */ 11810 181, /* Jul */ 11811 212, /* Aug */ 11812 243, /* Sep */ 11813 273, /* Oct */ 11814 304, /* Nov */ 11815 334, /* Dec */ 11816 365 11817 ]; 11818 11819 /** 11820 * the cumulative lengths of each month, for a leap year 11821 * @private 11822 * @const 11823 * @type Array.<number> 11824 */ 11825 JulianCal.cumMonthLengthsLeap = [ 11826 0, /* Jan */ 11827 31, /* Feb */ 11828 60, /* Mar */ 11829 91, /* Apr */ 11830 121, /* May */ 11831 152, /* Jun */ 11832 182, /* Jul */ 11833 213, /* Aug */ 11834 244, /* Sep */ 11835 274, /* Oct */ 11836 305, /* Nov */ 11837 335, /* Dec */ 11838 366 11839 ]; 11840 11841 /** 11842 * Return the number of months in the given year. The number of months in a year varies 11843 * for lunar calendars because in some years, an extra month is needed to extend the 11844 * days in a year to an entire solar year. The month is represented as a 1-based number 11845 * where 1=Jaunary, 2=February, etc. until 12=December. 11846 * 11847 * @param {number} year a year for which the number of months is sought 11848 */ 11849 JulianCal.prototype.getNumMonths = function(year) { 11850 return 12; 11851 }; 11852 11853 /** 11854 * Return the number of days in a particular month in a particular year. This function 11855 * can return a different number for a month depending on the year because of things 11856 * like leap years. 11857 * 11858 * @param {number} month the month for which the length is sought 11859 * @param {number} year the year within which that month can be found 11860 * @return {number} the number of days within the given month in the given year 11861 */ 11862 JulianCal.prototype.getMonLength = function(month, year) { 11863 if (month !== 2 || !this.isLeapYear(year)) { 11864 return JulianCal.monthLengths[month-1]; 11865 } else { 11866 return 29; 11867 } 11868 }; 11869 11870 /** 11871 * Return true if the given year is a leap year in the Julian calendar. 11872 * The year parameter may be given as a number, or as a JulDate object. 11873 * @param {number|JulianDate} year the year for which the leap year information is being sought 11874 * @return {boolean} true if the given year is a leap year 11875 */ 11876 JulianCal.prototype.isLeapYear = function(year) { 11877 var y = (typeof(year) === 'number' ? year : year.year); 11878 return MathUtils.mod(y, 4) === ((year > 0) ? 0 : 3); 11879 }; 11880 11881 /** 11882 * Return the type of this calendar. 11883 * 11884 * @return {string} the name of the type of this calendar 11885 */ 11886 JulianCal.prototype.getType = function() { 11887 return this.type; 11888 }; 11889 11890 11891 /* register this calendar for the factory method */ 11892 Calendar._constructors["julian"] = JulianCal; 11893 11894 11895 11896 /*< JulianRataDie.js */ 11897 /* 11898 * julianDate.js - Represent a date in the Julian calendar 11899 * 11900 * Copyright © 2012-2015, JEDLSoft 11901 * 11902 * Licensed under the Apache License, Version 2.0 (the "License"); 11903 * you may not use this file except in compliance with the License. 11904 * You may obtain a copy of the License at 11905 * 11906 * http://www.apache.org/licenses/LICENSE-2.0 11907 * 11908 * Unless required by applicable law or agreed to in writing, software 11909 * distributed under the License is distributed on an "AS IS" BASIS, 11910 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 11911 * 11912 * See the License for the specific language governing permissions and 11913 * limitations under the License. 11914 */ 11915 11916 /* !depends 11917 JulianCal.js 11918 RataDie.js 11919 */ 11920 11921 11922 /** 11923 * @class 11924 * Construct a new Julian RD date number object. The constructor parameters can 11925 * contain any of the following properties: 11926 * 11927 * <ul> 11928 * <li><i>unixtime<i> - sets the time of this instance according to the given 11929 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 11930 * 11931 * <li><i>julianday</i> - sets the time of this instance according to the given 11932 * Julian Day instance or the Julian Day given as a float 11933 * 11934 * <li><i>year</i> - any integer, including 0 11935 * 11936 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 11937 * 11938 * <li><i>day</i> - 1 to 31 11939 * 11940 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 11941 * is always done with an unambiguous 24 hour representation 11942 * 11943 * <li><i>minute</i> - 0 to 59 11944 * 11945 * <li><i>second</i> - 0 to 59 11946 * 11947 * <li><i>millisecond</i> - 0 to 999 11948 * 11949 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 11950 * </ul> 11951 * 11952 * If the constructor is called with another Julian date instance instead of 11953 * a parameter block, the other instance acts as a parameter block and its 11954 * settings are copied into the current instance.<p> 11955 * 11956 * If the constructor is called with no arguments at all or if none of the 11957 * properties listed above are present, then the RD is calculate based on 11958 * the current date at the time of instantiation. <p> 11959 * 11960 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 11961 * specified in the params, it is assumed that they have the smallest possible 11962 * value in the range for the property (zero or one).<p> 11963 * 11964 * 11965 * @private 11966 * @constructor 11967 * @extends RataDie 11968 * @param {Object=} params parameters that govern the settings and behaviour of this Julian RD date 11969 */ 11970 var JulianRataDie = function(params) { 11971 this.cal = params && params.cal || new JulianCal(); 11972 this.rd = NaN; 11973 RataDie.call(this, params); 11974 }; 11975 11976 JulianRataDie.prototype = new RataDie(); 11977 JulianRataDie.prototype.parent = RataDie; 11978 JulianRataDie.prototype.constructor = JulianRataDie; 11979 11980 /** 11981 * The difference between a zero Julian day and the first Julian date 11982 * of Friday, July 16, 622 CE Julian. 11983 * @private 11984 * @type number 11985 */ 11986 JulianRataDie.prototype.epoch = 1721422.5; 11987 11988 /** 11989 * Calculate the Rata Die (fixed day) number of the given date from the 11990 * date components. 11991 * 11992 * @protected 11993 * @param {Object} date the date components to calculate the RD from 11994 */ 11995 JulianRataDie.prototype._setDateComponents = function(date) { 11996 var year = date.year + ((date.year < 0) ? 1 : 0); 11997 var years = 365 * (year - 1) + Math.floor((year-1)/4); 11998 var dayInYear = (date.month > 1 ? JulianCal.cumMonthLengths[date.month-1] : 0) + 11999 date.day + 12000 (this.cal.isLeapYear(date.year) && date.month > 2 ? 1 : 0); 12001 var rdtime = (date.hour * 3600000 + 12002 date.minute * 60000 + 12003 date.second * 1000 + 12004 date.millisecond) / 12005 86400000; 12006 12007 /* 12008 console.log("calcRataDie: converting " + JSON.stringify(parts)); 12009 console.log("getRataDie: year is " + years); 12010 console.log("getRataDie: day in year is " + dayInYear); 12011 console.log("getRataDie: rdtime is " + rdtime); 12012 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 12013 */ 12014 12015 this.rd = years + dayInYear + rdtime; 12016 }; 12017 12018 12019 /*< JulianDate.js */ 12020 /* 12021 * JulianDate.js - Represent a date in the Julian calendar 12022 * 12023 * Copyright © 2012-2015, JEDLSoft 12024 * 12025 * Licensed under the Apache License, Version 2.0 (the "License"); 12026 * you may not use this file except in compliance with the License. 12027 * You may obtain a copy of the License at 12028 * 12029 * http://www.apache.org/licenses/LICENSE-2.0 12030 * 12031 * Unless required by applicable law or agreed to in writing, software 12032 * distributed under the License is distributed on an "AS IS" BASIS, 12033 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12034 * 12035 * See the License for the specific language governing permissions and 12036 * limitations under the License. 12037 */ 12038 12039 /* !depends 12040 ilib.js 12041 Locale.js 12042 IDate.js 12043 TimeZone.js 12044 JulianCal.js 12045 SearchUtils.js 12046 MathUtils.js 12047 LocaleInfo.js 12048 JulianRataDie.js 12049 */ 12050 12051 12052 12053 12054 /** 12055 * @class 12056 * Construct a new date object for the Julian Calendar. The constructor can be called 12057 * with a parameter object that contains any of the following properties: 12058 * 12059 * <ul> 12060 * <li><i>unixtime<i> - sets the time of this instance according to the given 12061 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 12062 * <li><i>julianday</i> - the Julian Day to set into this date 12063 * <li><i>year</i> - any integer except 0. Years go from -1 (BCE) to 1 (CE), skipping the zero 12064 * year which doesn't exist in the Julian calendar 12065 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12066 * <li><i>day</i> - 1 to 31 12067 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12068 * is always done with an unambiguous 24 hour representation 12069 * <li><i>minute</i> - 0 to 59 12070 * <li><i>second</i> - 0 to 59 12071 * <li><i>millisecond<i> - 0 to 999 12072 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 12073 * of this julian date. The date/time is kept in the local time. The time zone 12074 * is used later if this date is formatted according to a different time zone and 12075 * the difference has to be calculated, or when the date format has a time zone 12076 * component in it. 12077 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 12078 * given, it can be inferred from this locale. For locales that span multiple 12079 * time zones, the one with the largest population is chosen as the one that 12080 * represents the locale. 12081 * 12082 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 12083 * </ul> 12084 * 12085 * NB. The <a href="http://en.wikipedia.org/wiki/Julian_date">Julian Day</a> 12086 * (JulianDay) object is a <i>different</i> object than a 12087 * <a href="http://en.wikipedia.org/wiki/Julian_calendar">date in 12088 * the Julian calendar</a> and the two are not to be confused. The Julian Day 12089 * object represents time as a number of whole and fractional days since the 12090 * beginning of the epoch, whereas a date in the Julian 12091 * calendar is a regular date that signifies year, month, day, etc. using the rules 12092 * of the Julian calendar. The naming of Julian Days and the Julian calendar are 12093 * unfortunately close, and come from history.<p> 12094 * 12095 * If called with another Julian date argument, the date components of the given 12096 * date are copied into the current one.<p> 12097 * 12098 * If the constructor is called with no arguments at all or if none of the 12099 * properties listed above 12100 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12101 * components are 12102 * filled in with the current date at the time of instantiation. Note that if 12103 * you do not give the time zone when defaulting to the current time and the 12104 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12105 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12106 * Mean Time").<p> 12107 * 12108 * 12109 * @constructor 12110 * @extends IDate 12111 * @param {Object=} params parameters that govern the settings and behaviour of this Julian date 12112 */ 12113 var JulianDate = function(params) { 12114 this.cal = new JulianCal(); 12115 12116 params = params || {}; 12117 12118 if (params.timezone) { 12119 this.timezone = params.timezone; 12120 } 12121 if (params.locale) { 12122 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 12123 } 12124 12125 if (!this.timezone) { 12126 if (this.locale) { 12127 new LocaleInfo(this.locale, { 12128 sync: params.sync, 12129 loadParams: params.loadParams, 12130 onLoad: ilib.bind(this, function(li) { 12131 this.li = li; 12132 this.timezone = li.getTimeZone(); 12133 this._init(params); 12134 }) 12135 }); 12136 } else { 12137 this.timezone = "local"; 12138 this._init(params); 12139 } 12140 } else { 12141 this._init(params); 12142 } 12143 12144 }; 12145 12146 JulianDate.prototype = new IDate({noinstance: true}); 12147 JulianDate.prototype.parent = IDate; 12148 JulianDate.prototype.constructor = JulianDate; 12149 12150 /** 12151 * @private 12152 * Initialize the date 12153 */ 12154 JulianDate.prototype._init = function (params) { 12155 if (params.year || params.month || params.day || params.hour || 12156 params.minute || params.second || params.millisecond ) { 12157 /** 12158 * Year in the Julian calendar. 12159 * @type number 12160 */ 12161 this.year = parseInt(params.year, 10) || 0; 12162 /** 12163 * The month number, ranging from 1 (January) to 12 (December). 12164 * @type number 12165 */ 12166 this.month = parseInt(params.month, 10) || 1; 12167 /** 12168 * The day of the month. This ranges from 1 to 31. 12169 * @type number 12170 */ 12171 this.day = parseInt(params.day, 10) || 1; 12172 /** 12173 * The hour of the day. This can be a number from 0 to 23, as times are 12174 * stored unambiguously in the 24-hour clock. 12175 * @type number 12176 */ 12177 this.hour = parseInt(params.hour, 10) || 0; 12178 /** 12179 * The minute of the hours. Ranges from 0 to 59. 12180 * @type number 12181 */ 12182 this.minute = parseInt(params.minute, 10) || 0; 12183 /** 12184 * The second of the minute. Ranges from 0 to 59. 12185 * @type number 12186 */ 12187 this.second = parseInt(params.second, 10) || 0; 12188 /** 12189 * The millisecond of the second. Ranges from 0 to 999. 12190 * @type number 12191 */ 12192 this.millisecond = parseInt(params.millisecond, 10) || 0; 12193 12194 /** 12195 * The day of the year. Ranges from 1 to 383. 12196 * @type number 12197 */ 12198 this.dayOfYear = parseInt(params.dayOfYear, 10); 12199 12200 if (typeof(params.dst) === 'boolean') { 12201 this.dst = params.dst; 12202 } 12203 12204 this.rd = this.newRd(this); 12205 12206 new TimeZone({ 12207 id: this.timezone, 12208 sync: params.sync, 12209 loadParams: params.loadParams, 12210 onLoad: ilib.bind(this, function(tz) { 12211 this.tz = tz; 12212 // add the time zone offset to the rd to convert to UTC 12213 // getOffsetMillis requires that this.year, this.rd, and this.dst 12214 // are set in order to figure out which time zone rules apply and 12215 // what the offset is at that point in the year 12216 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 12217 if (this.offset !== 0) { 12218 this.rd = this.newRd({ 12219 rd: this.rd.getRataDie() - this.offset 12220 }); 12221 } 12222 this._init2(params); 12223 }) 12224 }); 12225 } else { 12226 this._init2(params); 12227 } 12228 }; 12229 12230 /** 12231 * @private 12232 * Finish initializing the date 12233 */ 12234 JulianDate.prototype._init2 = function (params) { 12235 if (!this.rd) { 12236 this.rd = this.newRd(params); 12237 this._calcDateComponents(); 12238 } 12239 12240 if (typeof(params.onLoad) === "function") { 12241 params.onLoad(this); 12242 } 12243 }; 12244 12245 /** 12246 * Return a new RD for this date type using the given params. 12247 * @protected 12248 * @param {Object=} params the parameters used to create this rata die instance 12249 * @returns {RataDie} the new RD instance for the given params 12250 */ 12251 JulianDate.prototype.newRd = function (params) { 12252 return new JulianRataDie(params); 12253 }; 12254 12255 /** 12256 * Return the year for the given RD 12257 * @protected 12258 * @param {number} rd RD to calculate from 12259 * @returns {number} the year for the RD 12260 */ 12261 JulianDate.prototype._calcYear = function(rd) { 12262 var year = Math.floor((4*(Math.floor(rd)-1) + 1464)/1461); 12263 12264 return (year <= 0) ? year - 1 : year; 12265 }; 12266 12267 /** 12268 * Calculate date components for the given RD date. 12269 * @protected 12270 */ 12271 JulianDate.prototype._calcDateComponents = function () { 12272 var remainder, 12273 cumulative, 12274 rd = this.rd.getRataDie(); 12275 12276 this.year = this._calcYear(rd); 12277 12278 if (typeof(this.offset) === "undefined") { 12279 this.year = this._calcYear(rd); 12280 12281 // now offset the RD by the time zone, then recalculate in case we were 12282 // near the year boundary 12283 if (!this.tz) { 12284 this.tz = new TimeZone({id: this.timezone}); 12285 } 12286 this.offset = this.tz.getOffsetMillis(this) / 86400000; 12287 } 12288 12289 if (this.offset !== 0) { 12290 rd += this.offset; 12291 this.year = this._calcYear(rd); 12292 } 12293 12294 var jan1 = this.newRd({ 12295 year: this.year, 12296 month: 1, 12297 day: 1, 12298 hour: 0, 12299 minute: 0, 12300 second: 0, 12301 millisecond: 0 12302 }); 12303 remainder = rd + 1 - jan1.getRataDie(); 12304 12305 cumulative = this.cal.isLeapYear(this.year) ? 12306 JulianCal.cumMonthLengthsLeap : 12307 JulianCal.cumMonthLengths; 12308 12309 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 12310 remainder = remainder - cumulative[this.month-1]; 12311 12312 this.day = Math.floor(remainder); 12313 remainder -= this.day; 12314 // now convert to milliseconds for the rest of the calculation 12315 remainder = Math.round(remainder * 86400000); 12316 12317 this.hour = Math.floor(remainder/3600000); 12318 remainder -= this.hour * 3600000; 12319 12320 this.minute = Math.floor(remainder/60000); 12321 remainder -= this.minute * 60000; 12322 12323 this.second = Math.floor(remainder/1000); 12324 remainder -= this.second * 1000; 12325 12326 this.millisecond = remainder; 12327 }; 12328 12329 /** 12330 * Return the day of the week of this date. The day of the week is encoded 12331 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 12332 * 12333 * @return {number} the day of the week 12334 */ 12335 JulianDate.prototype.getDayOfWeek = function() { 12336 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 12337 return MathUtils.mod(rd-2, 7); 12338 }; 12339 12340 /** 12341 * Return the name of the calendar that governs this date. 12342 * 12343 * @return {string} a string giving the name of the calendar 12344 */ 12345 JulianDate.prototype.getCalendar = function() { 12346 return "julian"; 12347 }; 12348 12349 //register with the factory method 12350 IDate._constructors["julian"] = JulianDate; 12351 12352 12353 /*< GregorianDate.js */ 12354 /* 12355 * GregorianDate.js - Represent a date in the Gregorian calendar 12356 * 12357 * Copyright © 2012-2015, 2018, JEDLSoft 12358 * 12359 * Licensed under the Apache License, Version 2.0 (the "License"); 12360 * you may not use this file except in compliance with the License. 12361 * You may obtain a copy of the License at 12362 * 12363 * http://www.apache.org/licenses/LICENSE-2.0 12364 * 12365 * Unless required by applicable law or agreed to in writing, software 12366 * distributed under the License is distributed on an "AS IS" BASIS, 12367 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12368 * 12369 * See the License for the specific language governing permissions and 12370 * limitations under the License. 12371 */ 12372 12373 /* !depends 12374 ilib.js 12375 IDate.js 12376 GregorianCal.js 12377 SearchUtils.js 12378 MathUtils.js 12379 Locale.js 12380 LocaleInfo.js 12381 GregRataDie.js 12382 TimeZone.js 12383 */ 12384 12385 12386 12387 12388 /** 12389 * @class 12390 * Construct a new Gregorian date object. The constructor parameters can 12391 * contain any of the following properties: 12392 * 12393 * <ul> 12394 * <li><i>unixtime<i> - sets the time of this instance according to the given 12395 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 12396 * 12397 * <li><i>julianday</i> - sets the time of this instance according to the given 12398 * Julian Day instance or the Julian Day given as a float 12399 * 12400 * <li><i>year</i> - any integer, including 0 12401 * 12402 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12403 * 12404 * <li><i>day</i> - 1 to 31 12405 * 12406 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12407 * is always done with an unambiguous 24 hour representation 12408 * 12409 * <li><i>minute</i> - 0 to 59 12410 * 12411 * <li><i>second</i> - 0 to 59 12412 * 12413 * <li><i>millisecond</i> - 0 to 999 12414 * 12415 * <li><i>dst</i> - boolean used to specify whether the given time components are 12416 * intended to be in daylight time or not. This is only used in the overlap 12417 * time when transitioning from DST to standard time, and the time components are 12418 * ambiguous. Otherwise at all other times of the year, this flag is ignored. 12419 * If you specify the date using unix time (UTC) or a julian day, then the time is 12420 * already unambiguous and this flag does not need to be specified. 12421 * <p> 12422 * For example, in the US, the transition out of daylight savings time 12423 * in 2014 happens at Nov 2, 2014 2:00am Daylight Time, when the time falls 12424 * back to Nov 2, 2014 1:00am Standard Time. If you give a date/time components as 12425 * "Nov 2, 2014 1:30am", then there are two 1:30am times in that day, and you would 12426 * have to give the standard flag to indicate which of those two you mean. 12427 * (dst=true means daylight time, dst=false means standard time). 12428 * 12429 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 12430 * of this gregorian date. The date/time is kept in the local time. The time zone 12431 * is used later if this date is formatted according to a different time zone and 12432 * the difference has to be calculated, or when the date format has a time zone 12433 * component in it. 12434 * 12435 * <li><i>locale</i> - locale for this gregorian date. If the time zone is not 12436 * given, it can be inferred from this locale. For locales that span multiple 12437 * time zones, the one with the largest population is chosen as the one that 12438 * represents the locale. 12439 * 12440 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 12441 * 12442 * <li><i>onLoad</i> - a callback function to call when this date object is fully 12443 * loaded. When the onLoad option is given, this date object will attempt to 12444 * load any missing locale data using the ilib loader callback. 12445 * When the constructor is done (even if the data is already preassembled), the 12446 * onLoad function is called with the current instance as a parameter, so this 12447 * callback can be used with preassembled or dynamic loading or a mix of the two. 12448 * 12449 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 12450 * asynchronously. If this option is given as "false", then the "onLoad" 12451 * callback must be given, as the instance returned from this constructor will 12452 * not be usable for a while. 12453 * 12454 * <li><i>loadParams</i> - an object containing parameters to pass to the 12455 * loader callback function when locale data is missing. The parameters are not 12456 * interpretted or modified in any way. They are simply passed along. The object 12457 * may contain any property/value pairs as long as the calling code is in 12458 * agreement with the loader callback function as to what those parameters mean. 12459 * </ul> 12460 * 12461 * If the constructor is called with another Gregorian date instance instead of 12462 * a parameter block, the other instance acts as a parameter block and its 12463 * settings are copied into the current instance.<p> 12464 * 12465 * If the constructor is called with no arguments at all or if none of the 12466 * properties listed above 12467 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12468 * components are 12469 * filled in with the current date at the time of instantiation. Note that if 12470 * you do not give the time zone when defaulting to the current time and the 12471 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12472 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12473 * Mean Time").<p> 12474 * 12475 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12476 * specified in the params, it is assumed that they have the smallest possible 12477 * value in the range for the property (zero or one).<p> 12478 * 12479 * 12480 * @constructor 12481 * @extends IDate 12482 * @param {Object=} params parameters that govern the settings and behaviour of this Gregorian date 12483 */ 12484 var GregorianDate = function(params) { 12485 this.cal = new GregorianCal(); 12486 12487 params = params || {}; 12488 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 12489 // for doing inheritance, so don't need to fill in the data. The 12490 // inheriting class only wants the methods. 12491 return; 12492 } 12493 12494 if (params.timezone) { 12495 this.timezone = params.timezone.toString(); 12496 } 12497 if (params.locale) { 12498 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 12499 } 12500 12501 if (!this.timezone) { 12502 if (this.locale) { 12503 new LocaleInfo(this.locale, { 12504 sync: params.sync, 12505 loadParams: params.loadParams, 12506 onLoad: ilib.bind(this, function(li) { 12507 this.li = li; 12508 this.timezone = li.getTimeZone(); 12509 this._init(params); 12510 }) 12511 }); 12512 } else { 12513 this.timezone = "local"; 12514 this._init(params); 12515 } 12516 } else { 12517 this._init(params); 12518 } 12519 }; 12520 12521 GregorianDate.prototype = new IDate({noinstance: true}); 12522 GregorianDate.prototype.parent = IDate; 12523 GregorianDate.prototype.constructor = GregorianDate; 12524 12525 /** 12526 * @private 12527 * Initialize this date object 12528 */ 12529 GregorianDate.prototype._init = function (params) { 12530 if (params.year || params.month || params.day || params.hour || 12531 params.minute || params.second || params.millisecond ) { 12532 this.year = parseInt(params.year, 10) || 0; 12533 this.month = parseInt(params.month, 10) || 1; 12534 this.day = parseInt(params.day, 10) || 1; 12535 this.hour = parseInt(params.hour, 10) || 0; 12536 this.minute = parseInt(params.minute, 10) || 0; 12537 this.second = parseInt(params.second, 10) || 0; 12538 this.millisecond = parseInt(params.millisecond, 10) || 0; 12539 if (typeof(params.dst) === 'boolean') { 12540 this.dst = params.dst; 12541 } 12542 this.rd = this.newRd(params); 12543 12544 // add the time zone offset to the rd to convert to UTC 12545 this.offset = 0; 12546 if (this.timezone === "local" && typeof(params.dst) === 'undefined') { 12547 // if dst is defined, the intrinsic Date object has no way of specifying which version of a time you mean 12548 // in the overlap time at the end of DST. Do you mean the daylight 1:30am or the standard 1:30am? In this 12549 // case, use the ilib calculations below, which can distinguish between the two properly 12550 var d = new Date(this.year, this.month-1, this.day, this.hour, this.minute, this.second, this.millisecond); 12551 var hBefore = new Date(this.year, this.month-1, this.day, this.hour - 1, this.minute, this.second, this.millisecond); 12552 this.offset = -d.getTimezoneOffset() / 1440; 12553 if (d.getTimezoneOffset() < hBefore.getTimezoneOffset()) { 12554 var startOffset = -hBefore.getTimezoneOffset() / 1440; 12555 this.rd = this.newRd({ 12556 rd: this.rd.getRataDie() - startOffset 12557 }); 12558 } else { 12559 this.rd = this.newRd({ 12560 rd: this.rd.getRataDie() - this.offset 12561 }); 12562 } 12563 this._init2(params); 12564 } else { 12565 new TimeZone({ 12566 id: this.timezone, 12567 sync: params.sync, 12568 loadParams: params.loadParams, 12569 onLoad: ilib.bind(this, function(tz) { 12570 this.tz = tz; 12571 12572 // getOffsetMillis requires that this.year, this.rd, and this.dst 12573 // are set in order to figure out which time zone rules apply and 12574 // what the offset is at that point in the year 12575 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 12576 this.rd = this.newRd({ 12577 rd: this.rd.getRataDie() - this.offset 12578 }); 12579 this._init2(params); 12580 }) 12581 }); 12582 } 12583 } else { 12584 this._init2(params); 12585 } 12586 }; 12587 12588 /** 12589 * @private 12590 * Finish initializing this date object 12591 */ 12592 GregorianDate.prototype._init2 = function (params) { 12593 if (!this.rd) { 12594 this.rd = this.newRd(params); 12595 this._calcDateComponents(); 12596 } 12597 12598 if (typeof(params.onLoad) === "function") { 12599 params.onLoad(this); 12600 } 12601 }; 12602 12603 /** 12604 * Return a new RD for this date type using the given params. 12605 * @private 12606 * @param {Object=} params the parameters used to create this rata die instance 12607 * @returns {RataDie} the new RD instance for the given params 12608 */ 12609 GregorianDate.prototype.newRd = function (params) { 12610 return new GregRataDie(params); 12611 }; 12612 12613 /** 12614 * Calculates the Gregorian year for a given rd number. 12615 * @private 12616 * @static 12617 */ 12618 GregorianDate._calcYear = function(rd) { 12619 var days400, 12620 days100, 12621 days4, 12622 years400, 12623 years100, 12624 years4, 12625 years1, 12626 year; 12627 12628 years400 = Math.floor((rd - 1) / 146097); 12629 days400 = MathUtils.mod((rd - 1), 146097); 12630 years100 = Math.floor(days400 / 36524); 12631 days100 = MathUtils.mod(days400, 36524); 12632 years4 = Math.floor(days100 / 1461); 12633 days4 = MathUtils.mod(days100, 1461); 12634 years1 = Math.floor(days4 / 365); 12635 12636 year = 400 * years400 + 100 * years100 + 4 * years4 + years1; 12637 if (years100 !== 4 && years1 !== 4) { 12638 year++; 12639 } 12640 return year; 12641 }; 12642 12643 /** 12644 * @private 12645 */ 12646 GregorianDate.prototype._calcYear = function(rd) { 12647 return GregorianDate._calcYear(rd); 12648 }; 12649 12650 /** 12651 * Calculate the date components for the current time zone 12652 * @private 12653 */ 12654 GregorianDate.prototype._calcDateComponents = function () { 12655 if (this.timezone === "local" && this.rd.getRataDie() >= -99280837 && this.rd.getRataDie() <= 100719163) { 12656 // console.log("using js Date to calculate offset"); 12657 // use the intrinsic JS Date object to do the tz conversion for us, which 12658 // guarantees that it follows the system tz database settings 12659 var d = new Date(this.rd.getTimeExtended()); 12660 12661 /** 12662 * Year in the Gregorian calendar. 12663 * @type number 12664 */ 12665 this.year = d.getFullYear(); 12666 12667 /** 12668 * The month number, ranging from 1 (January) to 12 (December). 12669 * @type number 12670 */ 12671 this.month = d.getMonth()+1; 12672 12673 /** 12674 * The day of the month. This ranges from 1 to 31. 12675 * @type number 12676 */ 12677 this.day = d.getDate(); 12678 12679 /** 12680 * The hour of the day. This can be a number from 0 to 23, as times are 12681 * stored unambiguously in the 24-hour clock. 12682 * @type number 12683 */ 12684 this.hour = d.getHours(); 12685 12686 /** 12687 * The minute of the hours. Ranges from 0 to 59. 12688 * @type number 12689 */ 12690 this.minute = d.getMinutes(); 12691 12692 /** 12693 * The second of the minute. Ranges from 0 to 59. 12694 * @type number 12695 */ 12696 this.second = d.getSeconds(); 12697 12698 /** 12699 * The millisecond of the second. Ranges from 0 to 999. 12700 * @type number 12701 */ 12702 this.millisecond = d.getMilliseconds(); 12703 12704 this.offset = -d.getTimezoneOffset() / 1440; 12705 } else { 12706 // console.log("using ilib to calculate offset. tz is " + this.timezone); 12707 // console.log("GregDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12708 if (typeof(this.offset) === "undefined") { 12709 // console.log("calculating offset"); 12710 this.year = this._calcYear(this.rd.getRataDie()); 12711 12712 // now offset the RD by the time zone, then recalculate in case we were 12713 // near the year boundary 12714 if (!this.tz) { 12715 this.tz = new TimeZone({id: this.timezone}); 12716 } 12717 this.offset = this.tz.getOffsetMillis(this) / 86400000; 12718 // } else { 12719 // console.log("offset is already defined somehow. type is " + typeof(this.offset)); 12720 // console.trace("Stack is this one"); 12721 } 12722 // console.log("offset is " + this.offset); 12723 var rd = this.rd.getRataDie(); 12724 if (this.offset !== 0) { 12725 rd += this.offset; 12726 } 12727 this.year = this._calcYear(rd); 12728 12729 var yearStartRd = this.newRd({ 12730 year: this.year, 12731 month: 1, 12732 day: 1, 12733 cal: this.cal 12734 }); 12735 12736 // remainder is days into the year 12737 var remainder = rd - yearStartRd.getRataDie() + 1; 12738 12739 var cumulative = GregorianCal.prototype.isLeapYear.call(this.cal, this.year) ? 12740 GregRataDie.cumMonthLengthsLeap : 12741 GregRataDie.cumMonthLengths; 12742 12743 this.month = SearchUtils.bsearch(Math.floor(remainder), cumulative); 12744 remainder = remainder - cumulative[this.month-1]; 12745 12746 this.day = Math.floor(remainder); 12747 remainder -= this.day; 12748 // now convert to milliseconds for the rest of the calculation 12749 remainder = Math.round(remainder * 86400000); 12750 12751 this.hour = Math.floor(remainder/3600000); 12752 remainder -= this.hour * 3600000; 12753 12754 this.minute = Math.floor(remainder/60000); 12755 remainder -= this.minute * 60000; 12756 12757 this.second = Math.floor(remainder/1000); 12758 remainder -= this.second * 1000; 12759 12760 this.millisecond = Math.floor(remainder); 12761 } 12762 }; 12763 12764 /** 12765 * Return the day of the week of this date. The day of the week is encoded 12766 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 12767 * 12768 * @return {number} the day of the week 12769 */ 12770 GregorianDate.prototype.getDayOfWeek = function() { 12771 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 12772 return MathUtils.mod(rd, 7); 12773 }; 12774 12775 /** 12776 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 12777 * 365, regardless of months or weeks, etc. That is, January 1st is day 1, and 12778 * December 31st is 365 in regular years, or 366 in leap years. 12779 * @return {number} the ordinal day of the year 12780 */ 12781 GregorianDate.prototype.getDayOfYear = function() { 12782 var cumulativeMap = this.cal.isLeapYear(this.year) ? 12783 GregRataDie.cumMonthLengthsLeap : 12784 GregRataDie.cumMonthLengths; 12785 12786 return cumulativeMap[this.month-1] + this.day; 12787 }; 12788 12789 /** 12790 * Return the era for this date as a number. The value for the era for Gregorian 12791 * calendars is -1 for "before the common era" (BCE) and 1 for "the common era" (CE). 12792 * BCE dates are any date before Jan 1, 1 CE. In the proleptic Gregorian calendar, 12793 * there is a year 0, so any years that are negative or zero are BCE. In the Julian 12794 * calendar, there is no year 0. Instead, the calendar goes straight from year -1 to 12795 * 1. 12796 * @return {number} 1 if this date is in the common era, -1 if it is before the 12797 * common era 12798 */ 12799 GregorianDate.prototype.getEra = function() { 12800 return (this.year < 1) ? -1 : 1; 12801 }; 12802 12803 /** 12804 * Return the name of the calendar that governs this date. 12805 * 12806 * @return {string} a string giving the name of the calendar 12807 */ 12808 GregorianDate.prototype.getCalendar = function() { 12809 return "gregorian"; 12810 }; 12811 12812 // register with the factory method 12813 IDate._constructors["gregorian"] = GregorianDate; 12814 12815 12816 12817 /*< ThaiSolarCal.js */ 12818 /* 12819 * ThaiSolarCal.js - Represent a Thai solar calendar object. 12820 * 12821 * Copyright © 2013-2015,2018, JEDLSoft 12822 * 12823 * Licensed under the Apache License, Version 2.0 (the "License"); 12824 * you may not use this file except in compliance with the License. 12825 * You may obtain a copy of the License at 12826 * 12827 * http://www.apache.org/licenses/LICENSE-2.0 12828 * 12829 * Unless required by applicable law or agreed to in writing, software 12830 * distributed under the License is distributed on an "AS IS" BASIS, 12831 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12832 * 12833 * See the License for the specific language governing permissions and 12834 * limitations under the License. 12835 */ 12836 12837 12838 /* !depends Calendar.js GregorianCal.js MathUtils.js */ 12839 12840 12841 /** 12842 * @class 12843 * Construct a new Thai solar calendar object. This class encodes information about 12844 * a Thai solar calendar.<p> 12845 * 12846 * @param {Object=} options Options governing the construction of this instance 12847 * @constructor 12848 * @extends Calendar 12849 */ 12850 var ThaiSolarCal = function(options) { 12851 this.type = "thaisolar"; 12852 12853 if (options && typeof(options.onLoad) === "function") { 12854 options.onLoad(this); 12855 } 12856 }; 12857 12858 ThaiSolarCal.prototype = new GregorianCal({noinstance: true}); 12859 ThaiSolarCal.prototype.parent = GregorianCal; 12860 ThaiSolarCal.prototype.constructor = ThaiSolarCal; 12861 12862 /** 12863 * Return true if the given year is a leap year in the Thai solar calendar. 12864 * The year parameter may be given as a number, or as a ThaiSolarDate object. 12865 * @param {number|ThaiSolarDate} year the year for which the leap year information is being sought 12866 * @return {boolean} true if the given year is a leap year 12867 */ 12868 ThaiSolarCal.prototype.isLeapYear = function(year) { 12869 var y = (typeof(year) === 'number' ? year : year.getYears()); 12870 y -= 543; 12871 var centuries = MathUtils.mod(y, 400); 12872 return (MathUtils.mod(y, 4) === 0 && centuries !== 100 && centuries !== 200 && centuries !== 300); 12873 }; 12874 12875 12876 /* register this calendar for the factory method */ 12877 Calendar._constructors["thaisolar"] = ThaiSolarCal; 12878 12879 12880 12881 /*< ThaiSolarDate.js */ 12882 /* 12883 * ThaiSolarDate.js - Represent a date in the ThaiSolar calendar 12884 * 12885 * Copyright © 2013-2015, JEDLSoft 12886 * 12887 * Licensed under the Apache License, Version 2.0 (the "License"); 12888 * you may not use this file except in compliance with the License. 12889 * You may obtain a copy of the License at 12890 * 12891 * http://www.apache.org/licenses/LICENSE-2.0 12892 * 12893 * Unless required by applicable law or agreed to in writing, software 12894 * distributed under the License is distributed on an "AS IS" BASIS, 12895 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12896 * 12897 * See the License for the specific language governing permissions and 12898 * limitations under the License. 12899 */ 12900 12901 /* !depends 12902 ilib.js 12903 IDate.js 12904 JSUtils.js 12905 GregorianDate.js 12906 ThaiSolarCal.js 12907 */ 12908 12909 12910 12911 12912 /** 12913 * @class 12914 * Construct a new Thai solar date object. The constructor parameters can 12915 * contain any of the following properties: 12916 * 12917 * <ul> 12918 * <li><i>unixtime<i> - sets the time of this instance according to the given 12919 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 12920 * 12921 * <li><i>julianday</i> - sets the time of this instance according to the given 12922 * Julian Day instance or the Julian Day given as a float 12923 * 12924 * <li><i>year</i> - any integer, including 0 12925 * 12926 * <li><i>month</i> - 1 to 12, where 1 means January, 2 means February, etc. 12927 * 12928 * <li><i>day</i> - 1 to 31 12929 * 12930 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 12931 * is always done with an unambiguous 24 hour representation 12932 * 12933 * <li><i>minute</i> - 0 to 59 12934 * 12935 * <li><i>second</i> - 0 to 59 12936 * 12937 * <li><i>millisecond</i> - 0 to 999 12938 * 12939 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 12940 * of this Thai solar date. The date/time is kept in the local time. The time zone 12941 * is used later if this date is formatted according to a different time zone and 12942 * the difference has to be calculated, or when the date format has a time zone 12943 * component in it. 12944 * 12945 * <li><i>locale</i> - locale for this Thai solar date. If the time zone is not 12946 * given, it can be inferred from this locale. For locales that span multiple 12947 * time zones, the one with the largest population is chosen as the one that 12948 * represents the locale. 12949 * </ul> 12950 * 12951 * If the constructor is called with another Thai solar date instance instead of 12952 * a parameter block, the other instance acts as a parameter block and its 12953 * settings are copied into the current instance.<p> 12954 * 12955 * If the constructor is called with no arguments at all or if none of the 12956 * properties listed above 12957 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 12958 * components are 12959 * filled in with the current date at the time of instantiation. Note that if 12960 * you do not give the time zone when defaulting to the current time and the 12961 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 12962 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 12963 * Mean Time").<p> 12964 * 12965 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 12966 * specified in the params, it is assumed that they have the smallest possible 12967 * value in the range for the property (zero or one).<p> 12968 * 12969 * 12970 * @constructor 12971 * @extends GregorianDate 12972 * @param {Object=} params parameters that govern the settings and behaviour of this Thai solar date 12973 */ 12974 var ThaiSolarDate = function(params) { 12975 var p = {}; 12976 12977 if (params) { 12978 JSUtils.shallowCopy(params, p); 12979 12980 // there is 198327 days difference between the Thai solar and 12981 // Gregorian epochs which is equivalent to 543 years 12982 if (typeof(p.year) !== 'undefined') { 12983 p.year -= 543; 12984 } 12985 if (typeof(p.rd) !== 'undefined') { 12986 p.rd -= 198327; 12987 } 12988 } 12989 this.rd = null; // clear these out so that the GregorianDate constructor can set it 12990 this.offset = undefined; 12991 //console.log("ThaiSolarDate.constructor: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 12992 12993 p.onLoad = ilib.bind(this, function(gd) { 12994 this.cal = new ThaiSolarCal(); 12995 12996 // make sure the year is set correctly from the original params 12997 if (params && typeof(params.year) !== 'undefined') { 12998 this.year = parseInt(params.year, 10); 12999 } 13000 13001 if (params && typeof(params.onLoad) === "function") { 13002 params.onLoad(gd); 13003 } 13004 }); 13005 13006 GregorianDate.call(this, p); 13007 }; 13008 13009 ThaiSolarDate.prototype = new GregorianDate({noinstance: true}); 13010 ThaiSolarDate.prototype.parent = GregorianDate.prototype; 13011 ThaiSolarDate.prototype.constructor = ThaiSolarDate; 13012 13013 /** 13014 * the difference between a zero Julian day and the zero Thai Solar date. 13015 * This is some 543 years before the start of the Gregorian epoch. 13016 * @private 13017 * @type number 13018 */ 13019 ThaiSolarDate.epoch = 1523097.5; 13020 13021 /** 13022 * Calculate the date components for the current time zone 13023 * @protected 13024 */ 13025 ThaiSolarDate.prototype._calcDateComponents = function () { 13026 // there is 198327 days difference between the Thai solar and 13027 // Gregorian epochs which is equivalent to 543 years 13028 // console.log("ThaiSolarDate._calcDateComponents: date is " + JSON.stringify(this) + " parent is " + JSON.stringify(this.parent) + " and parent.parent is " + JSON.stringify(this.parent.parent)); 13029 this.parent._calcDateComponents.call(this); 13030 this.year += 543; 13031 }; 13032 13033 /** 13034 * Return the Rata Die (fixed day) number of this date. 13035 * 13036 * @protected 13037 * @return {number} the rd date as a number 13038 */ 13039 ThaiSolarDate.prototype.getRataDie = function() { 13040 // there is 198327 days difference between the Thai solar and 13041 // Gregorian epochs which is equivalent to 543 years 13042 return this.rd.getRataDie() + 198327; 13043 }; 13044 13045 /** 13046 * Return a new Gregorian date instance that represents the first instance of the 13047 * given day of the week before the current date. The day of the week is encoded 13048 * as a number where 0 = Sunday, 1 = Monday, etc. 13049 * 13050 * @param {number} dow the day of the week before the current date that is being sought 13051 * @return {IDate} the date being sought 13052 */ 13053 ThaiSolarDate.prototype.before = function (dow) { 13054 return new ThaiSolarDate({ 13055 rd: this.rd.before(dow, this.offset) + 198327, 13056 timezone: this.timezone 13057 }); 13058 }; 13059 13060 /** 13061 * Return a new Gregorian date instance that represents the first instance of the 13062 * given day of the week after the current date. The day of the week is encoded 13063 * as a number where 0 = Sunday, 1 = Monday, etc. 13064 * 13065 * @param {number} dow the day of the week after the current date that is being sought 13066 * @return {IDate} the date being sought 13067 */ 13068 ThaiSolarDate.prototype.after = function (dow) { 13069 return new ThaiSolarDate({ 13070 rd: this.rd.after(dow, this.offset) + 198327, 13071 timezone: this.timezone 13072 }); 13073 }; 13074 13075 /** 13076 * Return a new Gregorian date instance that represents the first instance of the 13077 * given day of the week on or before the current date. The day of the week is encoded 13078 * as a number where 0 = Sunday, 1 = Monday, etc. 13079 * 13080 * @param {number} dow the day of the week on or before the current date that is being sought 13081 * @return {IDate} the date being sought 13082 */ 13083 ThaiSolarDate.prototype.onOrBefore = function (dow) { 13084 return new ThaiSolarDate({ 13085 rd: this.rd.onOrBefore(dow, this.offset) + 198327, 13086 timezone: this.timezone 13087 }); 13088 }; 13089 13090 /** 13091 * Return a new Gregorian date instance that represents the first instance of the 13092 * given day of the week on or after the current date. The day of the week is encoded 13093 * as a number where 0 = Sunday, 1 = Monday, etc. 13094 * 13095 * @param {number} dow the day of the week on or after the current date that is being sought 13096 * @return {IDate} the date being sought 13097 */ 13098 ThaiSolarDate.prototype.onOrAfter = function (dow) { 13099 return new ThaiSolarDate({ 13100 rd: this.rd.onOrAfter(dow, this.offset) + 198327, 13101 timezone: this.timezone 13102 }); 13103 }; 13104 13105 /** 13106 * Return the name of the calendar that governs this date. 13107 * 13108 * @return {string} a string giving the name of the calendar 13109 */ 13110 ThaiSolarDate.prototype.getCalendar = function() { 13111 return "thaisolar"; 13112 }; 13113 13114 //register with the factory method 13115 IDate._constructors["thaisolar"] = ThaiSolarDate; 13116 13117 13118 13119 /*< Astro.js */ 13120 /* 13121 * astro.js - Static functions to support astronomical calculations 13122 * 13123 * Copyright © 2014-2015, JEDLSoft 13124 * 13125 * Licensed under the Apache License, Version 2.0 (the "License"); 13126 * you may not use this file except in compliance with the License. 13127 * You may obtain a copy of the License at 13128 * 13129 * http://www.apache.org/licenses/LICENSE-2.0 13130 * 13131 * Unless required by applicable law or agreed to in writing, software 13132 * distributed under the License is distributed on an "AS IS" BASIS, 13133 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13134 * 13135 * See the License for the specific language governing permissions and 13136 * limitations under the License. 13137 */ 13138 13139 /* !depends 13140 ilib.js 13141 IDate.js 13142 Utils.js 13143 SearchUtils.js 13144 GregorianDate.js 13145 GregRataDie.js 13146 */ 13147 13148 // !data astro 13149 13150 /* 13151 * These routines were derived from a public domain set of JavaScript 13152 * functions for positional astronomy by John Walker of Fourmilab, 13153 * September 1999. 13154 */ 13155 13156 13157 13158 var Astro = {}; 13159 13160 /** 13161 * Load in all the data needed for astrological calculations. 13162 * 13163 * @private 13164 * @param {boolean} sync 13165 * @param {*} loadParams 13166 * @param {function(*)|undefined} callback 13167 */ 13168 Astro.initAstro = function(sync, loadParams, callback) { 13169 if (!ilib.data.astro) { 13170 Utils.loadData({ 13171 object: "Astro", 13172 name: "astro.json", // countries in their own language 13173 locale: "-", // only need to load the root file 13174 nonLocale: true, 13175 sync: sync, 13176 loadParams: loadParams, 13177 callback: ilib.bind(this, function(astroData) { 13178 /** 13179 * @type {{ 13180 * _EquinoxpTerms:Array.<number>, 13181 * _JDE0tab1000:Array.<number>, 13182 * _JDE0tab2000:Array.<number>, 13183 * _deltaTtab:Array.<number>, 13184 * _oterms:Array.<number>, 13185 * _nutArgMult:Array.<number>, 13186 * _nutArgCoeff:Array.<number>, 13187 * _nutCoeffA:Array.<number>, 13188 * _nutCoeffB:Array.<number>, 13189 * _coeff19th:Array.<number>, 13190 * _coeff18th:Array.<number>, 13191 * _solarLongCoeff:Array.<number>, 13192 * _solarLongMultipliers:Array.<number>, 13193 * _solarLongAddends:Array.<number>, 13194 * _meanMoonCoeff:Array.<number>, 13195 * _elongationCoeff:Array.<number>, 13196 * _solarAnomalyCoeff:Array.<number>, 13197 * _lunarAnomalyCoeff:Array.<number>, 13198 * _moonFromNodeCoeff:Array.<number>, 13199 * _eCoeff:Array.<number>, 13200 * _lunarElongationLongCoeff:Array.<number>, 13201 * _solarAnomalyLongCoeff:Array.<number>, 13202 * _lunarAnomalyLongCoeff:Array.<number>, 13203 * _moonFromNodeLongCoeff:Array.<number>, 13204 * _sineCoeff:Array.<number>, 13205 * _nmApproxCoeff:Array.<number>, 13206 * _nmCapECoeff:Array.<number>, 13207 * _nmSolarAnomalyCoeff:Array.<number>, 13208 * _nmLunarAnomalyCoeff:Array.<number>, 13209 * _nmMoonArgumentCoeff:Array.<number>, 13210 * _nmCapOmegaCoeff:Array.<number>, 13211 * _nmEFactor:Array.<number>, 13212 * _nmSolarCoeff:Array.<number>, 13213 * _nmLunarCoeff:Array.<number>, 13214 * _nmMoonCoeff:Array.<number>, 13215 * _nmSineCoeff:Array.<number>, 13216 * _nmAddConst:Array.<number>, 13217 * _nmAddCoeff:Array.<number>, 13218 * _nmAddFactor:Array.<number>, 13219 * _nmExtra:Array.<number> 13220 * }} 13221 */ 13222 ilib.data.astro = astroData; 13223 if (callback && typeof(callback) === 'function') { 13224 callback(astroData); 13225 } 13226 }) 13227 }); 13228 } else { 13229 if (callback && typeof(callback) === 'function') { 13230 callback(ilib.data.astro); 13231 } 13232 } 13233 }; 13234 13235 /** 13236 * Convert degrees to radians. 13237 * 13238 * @static 13239 * @protected 13240 * @param {number} d angle in degrees 13241 * @return {number} angle in radians 13242 */ 13243 Astro._dtr = function(d) { 13244 return (d * Math.PI) / 180.0; 13245 }; 13246 13247 /** 13248 * Convert radians to degrees. 13249 * 13250 * @static 13251 * @protected 13252 * @param {number} r angle in radians 13253 * @return {number} angle in degrees 13254 */ 13255 Astro._rtd = function(r) { 13256 return (r * 180.0) / Math.PI; 13257 }; 13258 13259 /** 13260 * Return the cosine of an angle given in degrees. 13261 * @static 13262 * @protected 13263 * @param {number} d angle in degrees 13264 * @return {number} cosine of the angle. 13265 */ 13266 Astro._dcos = function(d) { 13267 return Math.cos(Astro._dtr(d)); 13268 }; 13269 13270 /** 13271 * Return the sine of an angle given in degrees. 13272 * @static 13273 * @protected 13274 * @param {number} d angle in degrees 13275 * @return {number} sine of the angle. 13276 */ 13277 Astro._dsin = function(d) { 13278 return Math.sin(Astro._dtr(d)); 13279 }; 13280 13281 /** 13282 * Return the tan of an angle given in degrees. 13283 * @static 13284 * @protected 13285 * @param {number} d angle in degrees 13286 * @return {number} tan of the angle. 13287 */ 13288 Astro._dtan = function(d) { 13289 return Math.tan(Astro._dtr(d)); 13290 }; 13291 13292 /** 13293 * Range reduce angle in degrees. 13294 * 13295 * @static 13296 * @param {number} a angle to reduce 13297 * @return {number} the reduced angle 13298 */ 13299 Astro._fixangle = function(a) { 13300 return a - 360.0 * (Math.floor(a / 360.0)); 13301 }; 13302 13303 /** 13304 * Range reduce angle in radians. 13305 * 13306 * @static 13307 * @protected 13308 * @param {number} a angle to reduce 13309 * @return {number} the reduced angle 13310 */ 13311 Astro._fixangr = function(a) { 13312 return a - (2 * Math.PI) * (Math.floor(a / (2 * Math.PI))); 13313 }; 13314 13315 /** 13316 * Determine the Julian Ephemeris Day of an equinox or solstice. The "which" 13317 * argument selects the item to be computed: 13318 * 13319 * <ul> 13320 * <li>0 March equinox 13321 * <li>1 June solstice 13322 * <li>2 September equinox 13323 * <li>3 December solstice 13324 * </ul> 13325 * 13326 * @static 13327 * @protected 13328 * @param {number} year Gregorian year to calculate for 13329 * @param {number} which Which equinox or solstice to calculate 13330 */ 13331 Astro._equinox = function(year, which) { 13332 var deltaL, i, j, JDE0, JDE, JDE0tab, S, T, W, Y; 13333 13334 /* Initialize terms for mean equinox and solstices. We 13335 have two sets: one for years prior to 1000 and a second 13336 for subsequent years. */ 13337 13338 if (year < 1000) { 13339 JDE0tab = ilib.data.astro._JDE0tab1000; 13340 Y = year / 1000; 13341 } else { 13342 JDE0tab = ilib.data.astro._JDE0tab2000; 13343 Y = (year - 2000) / 1000; 13344 } 13345 13346 JDE0 = JDE0tab[which][0] + (JDE0tab[which][1] * Y) 13347 + (JDE0tab[which][2] * Y * Y) + (JDE0tab[which][3] * Y * Y * Y) 13348 + (JDE0tab[which][4] * Y * Y * Y * Y); 13349 13350 //document.debug.log.value += "JDE0 = " + JDE0 + "\n"; 13351 13352 T = (JDE0 - 2451545.0) / 36525; 13353 //document.debug.log.value += "T = " + T + "\n"; 13354 W = (35999.373 * T) - 2.47; 13355 //document.debug.log.value += "W = " + W + "\n"; 13356 deltaL = 1 + (0.0334 * Astro._dcos(W)) + (0.0007 * Astro._dcos(2 * W)); 13357 //document.debug.log.value += "deltaL = " + deltaL + "\n"; 13358 13359 // Sum the periodic terms for time T 13360 13361 S = 0; 13362 j = 0; 13363 for (i = 0; i < 24; i++) { 13364 S += ilib.data.astro._EquinoxpTerms[j] 13365 * Astro._dcos(ilib.data.astro._EquinoxpTerms[j + 1] + (ilib.data.astro._EquinoxpTerms[j + 2] * T)); 13366 j += 3; 13367 } 13368 13369 //document.debug.log.value += "S = " + S + "\n"; 13370 //document.debug.log.value += "Corr = " + ((S * 0.00001) / deltaL) + "\n"; 13371 13372 JDE = JDE0 + ((S * 0.00001) / deltaL); 13373 13374 return JDE; 13375 }; 13376 13377 /* 13378 * The table of observed Delta T values at the beginning of 13379 * years from 1620 through 2014 as found in astro.json is taken from 13380 * http://www.staff.science.uu.nl/~gent0113/deltat/deltat.htm 13381 * and 13382 * ftp://maia.usno.navy.mil/ser7/deltat.data 13383 */ 13384 13385 /** 13386 * Determine the difference, in seconds, between dynamical time and universal time. 13387 * 13388 * @static 13389 * @protected 13390 * @param {number} year to calculate the difference for 13391 * @return {number} difference in seconds between dynamical time and universal time 13392 */ 13393 Astro._deltat = function (year) { 13394 var dt, f, i, t; 13395 13396 if ((year >= 1620) && (year <= 2014)) { 13397 i = Math.floor(year - 1620); 13398 f = (year - 1620) - i; /* Fractional part of year */ 13399 dt = ilib.data.astro._deltaTtab[i] + ((ilib.data.astro._deltaTtab[i + 1] - ilib.data.astro._deltaTtab[i]) * f); 13400 } else { 13401 t = (year - 2000) / 100; 13402 if (year < 948) { 13403 dt = 2177 + (497 * t) + (44.1 * t * t); 13404 } else { 13405 dt = 102 + (102 * t) + (25.3 * t * t); 13406 if ((year > 2000) && (year < 2100)) { 13407 dt += 0.37 * (year - 2100); 13408 } 13409 } 13410 } 13411 return dt; 13412 }; 13413 13414 /** 13415 * Calculate the obliquity of the ecliptic for a given 13416 * Julian date. This uses Laskar's tenth-degree 13417 * polynomial fit (J. Laskar, Astronomy and 13418 * Astrophysics, Vol. 157, page 68 [1986]) which is 13419 * accurate to within 0.01 arc second between AD 1000 13420 * and AD 3000, and within a few seconds of arc for 13421 * +/-10000 years around AD 2000. If we're outside the 13422 * range in which this fit is valid (deep time) we 13423 * simply return the J2000 value of the obliquity, which 13424 * happens to be almost precisely the mean. 13425 * 13426 * @static 13427 * @protected 13428 * @param {number} jd Julian Day to calculate the obliquity for 13429 * @return {number} the obliquity 13430 */ 13431 Astro._obliqeq = function (jd) { 13432 var eps, u, v, i; 13433 13434 v = u = (jd - 2451545.0) / 3652500.0; 13435 13436 eps = 23 + (26 / 60.0) + (21.448 / 3600.0); 13437 13438 if (Math.abs(u) < 1.0) { 13439 for (i = 0; i < 10; i++) { 13440 eps += (ilib.data.astro._oterms[i] / 3600.0) * v; 13441 v *= u; 13442 } 13443 } 13444 return eps; 13445 }; 13446 13447 /** 13448 * Return the position of the sun. We return 13449 * intermediate values because they are useful in a 13450 * variety of other contexts. 13451 * @static 13452 * @protected 13453 * @param {number} jd find the position of sun on this Julian Day 13454 * @return {Object} the position of the sun and many intermediate 13455 * values 13456 */ 13457 Astro._sunpos = function(jd) { 13458 var ret = {}, 13459 T, T2, T3, Omega, epsilon, epsilon0; 13460 13461 T = (jd - 2451545.0) / 36525.0; 13462 //document.debug.log.value += "Sunpos. T = " + T + "\n"; 13463 T2 = T * T; 13464 T3 = T * T2; 13465 ret.meanLongitude = Astro._fixangle(280.46646 + 36000.76983 * T + 0.0003032 * T2); 13466 //document.debug.log.value += "ret.meanLongitude = " + ret.meanLongitude + "\n"; 13467 ret.meanAnomaly = Astro._fixangle(357.52911 + (35999.05029 * T) - 0.0001537 * T2 - 0.00000048 * T3); 13468 //document.debug.log.value += "ret.meanAnomaly = " + ret.meanAnomaly + "\n"; 13469 ret.eccentricity = 0.016708634 - 0.000042037 * T - 0.0000001267 * T2; 13470 //document.debug.log.value += "e = " + e + "\n"; 13471 ret.equationOfCenter = ((1.914602 - 0.004817 * T - 0.000014 * T2) * Astro._dsin(ret.meanAnomaly)) 13472 + ((0.019993 - 0.000101 * T) * Astro._dsin(2 * ret.meanAnomaly)) 13473 + (0.000289 * Astro._dsin(3 * ret.meanAnomaly)); 13474 //document.debug.log.value += "ret.equationOfCenter = " + ret.equationOfCenter + "\n"; 13475 ret.sunLongitude = ret.meanLongitude + ret.equationOfCenter; 13476 //document.debug.log.value += "ret.sunLongitude = " + ret.sunLongitude + "\n"; 13477 //ret.sunAnomaly = ret.meanAnomaly + ret.equationOfCenter; 13478 //document.debug.log.value += "ret.sunAnomaly = " + ret.sunAnomaly + "\n"; 13479 // ret.sunRadius = (1.000001018 * (1 - (ret.eccentricity * ret.eccentricity))) / (1 + (ret.eccentricity * Astro._dcos(ret.sunAnomaly))); 13480 //document.debug.log.value += "ret.sunRadius = " + ret.sunRadius + "\n"; 13481 Omega = 125.04 - (1934.136 * T); 13482 //document.debug.log.value += "Omega = " + Omega + "\n"; 13483 ret.apparentLong = ret.sunLongitude + (-0.00569) + (-0.00478 * Astro._dsin(Omega)); 13484 //document.debug.log.value += "ret.apparentLong = " + ret.apparentLong + "\n"; 13485 epsilon0 = Astro._obliqeq(jd); 13486 //document.debug.log.value += "epsilon0 = " + epsilon0 + "\n"; 13487 epsilon = epsilon0 + (0.00256 * Astro._dcos(Omega)); 13488 //document.debug.log.value += "epsilon = " + epsilon + "\n"; 13489 //ret.rightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon0) * Astro._dsin(ret.sunLongitude), Astro._dcos(ret.sunLongitude)))); 13490 //document.debug.log.value += "ret.rightAscension = " + ret.rightAscension + "\n"; 13491 // ret.declination = Astro._rtd(Math.asin(Astro._dsin(epsilon0) * Astro._dsin(ret.sunLongitude))); 13492 ////document.debug.log.value += "ret.declination = " + ret.declination + "\n"; 13493 ret.inclination = Astro._fixangle(23.4392911 - 0.013004167 * T - 0.00000016389 * T2 + 0.0000005036 * T3); 13494 ret.apparentRightAscension = Astro._fixangle(Astro._rtd(Math.atan2(Astro._dcos(epsilon) * Astro._dsin(ret.apparentLong), Astro._dcos(ret.apparentLong)))); 13495 //document.debug.log.value += "ret.apparentRightAscension = " + ret.apparentRightAscension + "\n"; 13496 //ret.apparentDeclination = Astro._rtd(Math.asin(Astro._dsin(epsilon) * Astro._dsin(ret.apparentLong))); 13497 //document.debug.log.value += "ret.apparentDecliation = " + ret.apparentDecliation + "\n"; 13498 13499 // Angular quantities are expressed in decimal degrees 13500 return ret; 13501 }; 13502 13503 /** 13504 * Calculate the nutation in longitude, deltaPsi, and obliquity, 13505 * deltaEpsilon for a given Julian date jd. Results are returned as an object 13506 * giving deltaPsi and deltaEpsilon in degrees. 13507 * 13508 * @static 13509 * @protected 13510 * @param {number} jd calculate the nutation of this Julian Day 13511 * @return {Object} the deltaPsi and deltaEpsilon of the nutation 13512 */ 13513 Astro._nutation = function(jd) { 13514 var i, j, 13515 t = (jd - 2451545.0) / 36525.0, 13516 t2, t3, to10, 13517 ta = [], 13518 dp = 0, 13519 de = 0, 13520 ang, 13521 ret = {}; 13522 13523 t3 = t * (t2 = t * t); 13524 13525 /* 13526 * Calculate angles. The correspondence between the elements of our array 13527 * and the terms cited in Meeus are: 13528 * 13529 * ta[0] = D ta[0] = M ta[2] = M' ta[3] = F ta[4] = \Omega 13530 * 13531 */ 13532 13533 ta[0] = Astro._dtr(297.850363 + 445267.11148 * t - 0.0019142 * t2 + t3 / 189474.0); 13534 ta[1] = Astro._dtr(357.52772 + 35999.05034 * t - 0.0001603 * t2 - t3 / 300000.0); 13535 ta[2] = Astro._dtr(134.96298 + 477198.867398 * t + 0.0086972 * t2 + t3 / 56250.0); 13536 ta[3] = Astro._dtr(93.27191 + 483202.017538 * t - 0.0036825 * t2 + t3 / 327270); 13537 ta[4] = Astro._dtr(125.04452 - 1934.136261 * t + 0.0020708 * t2 + t3 / 450000.0); 13538 13539 /* 13540 * Range reduce the angles in case the sine and cosine functions don't do it 13541 * as accurately or quickly. 13542 */ 13543 13544 for (i = 0; i < 5; i++) { 13545 ta[i] = Astro._fixangr(ta[i]); 13546 } 13547 13548 to10 = t / 10.0; 13549 for (i = 0; i < 63; i++) { 13550 ang = 0; 13551 for (j = 0; j < 5; j++) { 13552 if (ilib.data.astro._nutArgMult[(i * 5) + j] != 0) { 13553 ang += ilib.data.astro._nutArgMult[(i * 5) + j] * ta[j]; 13554 } 13555 } 13556 dp += (ilib.data.astro._nutArgCoeff[(i * 4) + 0] + ilib.data.astro._nutArgCoeff[(i * 4) + 1] * to10) * Math.sin(ang); 13557 de += (ilib.data.astro._nutArgCoeff[(i * 4) + 2] + ilib.data.astro._nutArgCoeff[(i * 4) + 3] * to10) * Math.cos(ang); 13558 } 13559 13560 /* 13561 * Return the result, converting from ten thousandths of arc seconds to 13562 * radians in the process. 13563 */ 13564 13565 ret.deltaPsi = dp / (3600.0 * 10000.0); 13566 ret.deltaEpsilon = de / (3600.0 * 10000.0); 13567 13568 return ret; 13569 }; 13570 13571 /** 13572 * Returns the equation of time as a fraction of a day. 13573 * 13574 * @static 13575 * @protected 13576 * @param {number} jd the Julian Day of the day to calculate for 13577 * @return {number} the equation of time for the given day 13578 */ 13579 Astro._equationOfTime = function(jd) { 13580 var alpha, deltaPsi, E, epsilon, L0, tau, pos; 13581 13582 // 2451545.0 is the Julian day of J2000 epoch 13583 // 365250.0 is the number of days in a Julian millenium 13584 tau = (jd - 2451545.0) / 365250.0; 13585 //console.log("equationOfTime. tau = " + tau); 13586 L0 = 280.4664567 + (360007.6982779 * tau) + (0.03032028 * tau * tau) 13587 + ((tau * tau * tau) / 49931) 13588 + (-((tau * tau * tau * tau) / 15300)) 13589 + (-((tau * tau * tau * tau * tau) / 2000000)); 13590 //console.log("L0 = " + L0); 13591 L0 = Astro._fixangle(L0); 13592 //console.log("L0 = " + L0); 13593 pos = Astro._sunpos(jd); 13594 alpha = pos.apparentRightAscension; 13595 //console.log("alpha = " + alpha); 13596 var nut = Astro._nutation(jd); 13597 deltaPsi = nut.deltaPsi; 13598 //console.log("deltaPsi = " + deltaPsi); 13599 epsilon = Astro._obliqeq(jd) + nut.deltaEpsilon; 13600 //console.log("epsilon = " + epsilon); 13601 //console.log("L0 - 0.0057183 = " + (L0 - 0.0057183)); 13602 //console.log("L0 - 0.0057183 - alpha = " + (L0 - 0.0057183 - alpha)); 13603 //console.log("deltaPsi * cos(epsilon) = " + deltaPsi * Astro._dcos(epsilon)); 13604 13605 E = L0 - 0.0057183 - alpha + deltaPsi * Astro._dcos(epsilon); 13606 // if alpha and L0 are in different quadrants, then renormalize 13607 // so that the difference between them is in the right range 13608 if (E > 180) { 13609 E -= 360; 13610 } 13611 //console.log("E = " + E); 13612 // E = E - 20.0 * (Math.floor(E / 20.0)); 13613 E = E * 4; 13614 //console.log("Efixed = " + E); 13615 E = E / (24 * 60); 13616 //console.log("Eday = " + E); 13617 13618 return E; 13619 }; 13620 13621 /** 13622 * @private 13623 * @static 13624 */ 13625 Astro._poly = function(x, coefficients) { 13626 var result = coefficients[0]; 13627 var xpow = x; 13628 for (var i = 1; i < coefficients.length; i++) { 13629 result += coefficients[i] * xpow; 13630 xpow *= x; 13631 } 13632 return result; 13633 }; 13634 13635 /** 13636 * Calculate the UTC RD from the local RD given "zone" number of minutes 13637 * worth of offset. 13638 * 13639 * @static 13640 * @protected 13641 * @param {number} local RD of the locale time, given in any calendar 13642 * @param {number} zone number of minutes of offset from UTC for the time zone 13643 * @return {number} the UTC equivalent of the local RD 13644 */ 13645 Astro._universalFromLocal = function(local, zone) { 13646 return local - zone / 1440; 13647 }; 13648 13649 /** 13650 * Calculate the local RD from the UTC RD given "zone" number of minutes 13651 * worth of offset. 13652 * 13653 * @static 13654 * @protected 13655 * @param {number} local RD of the locale time, given in any calendar 13656 * @param {number} zone number of minutes of offset from UTC for the time zone 13657 * @return {number} the UTC equivalent of the local RD 13658 */ 13659 Astro._localFromUniversal = function(local, zone) { 13660 return local + zone / 1440; 13661 }; 13662 13663 /** 13664 * @private 13665 * @static 13666 * @param {number} c julian centuries of the date to calculate 13667 * @return {number} the aberration 13668 */ 13669 Astro._aberration = function(c) { 13670 return 9.74e-05 * Astro._dcos(177.63 + 35999.01847999999 * c) - 0.005575; 13671 }; 13672 13673 /** 13674 * @private 13675 * 13676 ilib.data.astro._nutCoeffA = [124.90, -1934.134, 0.002063]; 13677 ilib.data.astro._nutCoeffB q= [201.11, 72001.5377, 0.00057]; 13678 */ 13679 13680 /** 13681 * @private 13682 * @static 13683 * @param {number} c julian centuries of the date to calculate 13684 * @return {number} the nutation for the given julian century in radians 13685 */ 13686 Astro._nutation2 = function(c) { 13687 var a = Astro._poly(c, ilib.data.astro._nutCoeffA); 13688 var b = Astro._poly(c, ilib.data.astro._nutCoeffB); 13689 // return -0.0000834 * Astro._dsin(a) - 0.0000064 * Astro._dsin(b); 13690 return -0.004778 * Astro._dsin(a) - 0.0003667 * Astro._dsin(b); 13691 }; 13692 13693 /** 13694 * @static 13695 * @private 13696 */ 13697 Astro._ephemerisCorrection = function(jd) { 13698 var year = GregorianDate._calcYear(jd - 1721424.5); 13699 13700 if (1988 <= year && year <= 2019) { 13701 return (year - 1933) / 86400; 13702 } 13703 13704 if (1800 <= year && year <= 1987) { 13705 var jul1 = new GregRataDie({ 13706 year: year, 13707 month: 7, 13708 day: 1, 13709 hour: 0, 13710 minute: 0, 13711 second: 0 13712 }); 13713 // 693596 is the rd of Jan 1, 1900 13714 var theta = (jul1.getRataDie() - 693596) / 36525; 13715 return Astro._poly(theta, (1900 <= year) ? ilib.data.astro._coeff19th : ilib.data.astro._coeff18th); 13716 } 13717 13718 if (1620 <= year && year <= 1799) { 13719 year -= 1600; 13720 return (196.58333 - 4.0675 * year + 0.0219167 * year * year) / 86400; 13721 } 13722 13723 // 660724 is the rd of Jan 1, 1810 13724 var jan1 = new GregRataDie({ 13725 year: year, 13726 month: 1, 13727 day: 1, 13728 hour: 0, 13729 minute: 0, 13730 second: 0 13731 }); 13732 // var x = 0.5 + (jan1.getRataDie() - 660724); 13733 var x = 0.5 + (jan1.getRataDie() - 660724); 13734 13735 return ((x * x / 41048480) - 15) / 86400; 13736 }; 13737 13738 /** 13739 * @static 13740 * @private 13741 */ 13742 Astro._ephemerisFromUniversal = function(jd) { 13743 return jd + Astro._ephemerisCorrection(jd); 13744 }; 13745 13746 /** 13747 * @static 13748 * @private 13749 */ 13750 Astro._universalFromEphemeris = function(jd) { 13751 return jd - Astro._ephemerisCorrection(jd); 13752 }; 13753 13754 /** 13755 * @static 13756 * @private 13757 */ 13758 Astro._julianCenturies = function(jd) { 13759 // 2451545.0 is the Julian day of J2000 epoch 13760 // 730119.5 is the Gregorian RD of J2000 epoch 13761 // 36525.0 is the number of days in a Julian century 13762 return (Astro._ephemerisFromUniversal(jd) - 2451545.0) / 36525.0; 13763 }; 13764 13765 /** 13766 * Calculate the solar longitude 13767 * 13768 * @static 13769 * @protected 13770 * @param {number} jd julian day of the date to calculate the longitude for 13771 * @return {number} the solar longitude in degrees 13772 */ 13773 Astro._solarLongitude = function(jd) { 13774 var c = Astro._julianCenturies(jd), 13775 longitude = 0, 13776 len = ilib.data.astro._solarLongCoeff.length; 13777 13778 for (var i = 0; i < len; i++) { 13779 longitude += ilib.data.astro._solarLongCoeff[i] * 13780 Astro._dsin(ilib.data.astro._solarLongAddends[i] + ilib.data.astro._solarLongMultipliers[i] * c); 13781 } 13782 longitude *= 5.729577951308232e-06; 13783 longitude += 282.77718340000001 + 36000.769537439999 * c; 13784 longitude += Astro._aberration(c) + Astro._nutation2(c); 13785 return Astro._fixangle(longitude); 13786 }; 13787 13788 /** 13789 * @static 13790 * @protected 13791 * @param {number} jd 13792 * @return {number} 13793 */ 13794 Astro._lunarLongitude = function (jd) { 13795 var c = Astro._julianCenturies(jd), 13796 meanMoon = Astro._fixangle(Astro._poly(c, ilib.data.astro._meanMoonCoeff)), 13797 elongation = Astro._fixangle(Astro._poly(c, ilib.data.astro._elongationCoeff)), 13798 solarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._solarAnomalyCoeff)), 13799 lunarAnomaly = Astro._fixangle(Astro._poly(c, ilib.data.astro._lunarAnomalyCoeff)), 13800 moonNode = Astro._fixangle(Astro._poly(c, ilib.data.astro._moonFromNodeCoeff)), 13801 e = Astro._poly(c, ilib.data.astro._eCoeff); 13802 13803 var sum = 0; 13804 for (var i = 0; i < ilib.data.astro._lunarElongationLongCoeff.length; i++) { 13805 var x = ilib.data.astro._solarAnomalyLongCoeff[i]; 13806 13807 sum += ilib.data.astro._sineCoeff[i] * Math.pow(e, Math.abs(x)) * 13808 Astro._dsin(ilib.data.astro._lunarElongationLongCoeff[i] * elongation + x * solarAnomaly + 13809 ilib.data.astro._lunarAnomalyLongCoeff[i] * lunarAnomaly + 13810 ilib.data.astro._moonFromNodeLongCoeff[i] * moonNode); 13811 } 13812 var longitude = sum / 1000000; 13813 var venus = 3958.0 / 1000000 * Astro._dsin(119.75 + c * 131.84899999999999); 13814 var jupiter = 318.0 / 1000000 * Astro._dsin(53.090000000000003 + c * 479264.28999999998); 13815 var flatEarth = 1962.0 / 1000000 * Astro._dsin(meanMoon - moonNode); 13816 13817 return Astro._fixangle(meanMoon + longitude + venus + jupiter + flatEarth + Astro._nutation2(c)); 13818 }; 13819 13820 /** 13821 * @static 13822 * @protected 13823 * @param {number} n 13824 * @return {number} julian day of the n'th new moon 13825 */ 13826 Astro._newMoonTime = function(n) { 13827 var k = n - 24724; 13828 var c = k / 1236.8499999999999; 13829 var approx = Astro._poly(c, ilib.data.astro._nmApproxCoeff); 13830 var capE = Astro._poly(c, ilib.data.astro._nmCapECoeff); 13831 var solarAnomaly = Astro._poly(c, ilib.data.astro._nmSolarAnomalyCoeff); 13832 var lunarAnomaly = Astro._poly(c, ilib.data.astro._nmLunarAnomalyCoeff); 13833 var moonArgument = Astro._poly(c, ilib.data.astro._nmMoonArgumentCoeff); 13834 var capOmega = Astro._poly(c, ilib.data.astro._nmCapOmegaCoeff); 13835 var correction = -0.00017 * Astro._dsin(capOmega); 13836 for (var i = 0; i < ilib.data.astro._nmSineCoeff.length; i++) { 13837 correction = correction + ilib.data.astro._nmSineCoeff[i] * Math.pow(capE, ilib.data.astro._nmEFactor[i]) * 13838 Astro._dsin(ilib.data.astro._nmSolarCoeff[i] * solarAnomaly + 13839 ilib.data.astro._nmLunarCoeff[i] * lunarAnomaly + 13840 ilib.data.astro._nmMoonCoeff[i] * moonArgument); 13841 } 13842 var additional = 0; 13843 for (var i = 0; i < ilib.data.astro._nmAddConst.length; i++) { 13844 additional = additional + ilib.data.astro._nmAddFactor[i] * 13845 Astro._dsin(ilib.data.astro._nmAddConst[i] + ilib.data.astro._nmAddCoeff[i] * k); 13846 } 13847 var extra = 0.000325 * Astro._dsin(Astro._poly(c, ilib.data.astro._nmExtra)); 13848 return Astro._universalFromEphemeris(approx + correction + extra + additional + RataDie.gregorianEpoch); 13849 }; 13850 13851 /** 13852 * @static 13853 * @protected 13854 * @param {number} jd 13855 * @return {number} 13856 */ 13857 Astro._lunarSolarAngle = function(jd) { 13858 var lunar = Astro._lunarLongitude(jd); 13859 var solar = Astro._solarLongitude(jd) 13860 return Astro._fixangle(lunar - solar); 13861 }; 13862 13863 /** 13864 * @static 13865 * @protected 13866 * @param {number} jd 13867 * @return {number} 13868 */ 13869 Astro._newMoonBefore = function (jd) { 13870 var phase = Astro._lunarSolarAngle(jd); 13871 // 11.450086114414322 is the julian day of the 0th full moon 13872 // 29.530588853000001 is the average length of a month 13873 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360) - 1; 13874 var current, last; 13875 current = last = Astro._newMoonTime(guess); 13876 while (current < jd) { 13877 guess++; 13878 last = current; 13879 current = Astro._newMoonTime(guess); 13880 } 13881 return last; 13882 }; 13883 13884 /** 13885 * @static 13886 * @protected 13887 * @param {number} jd 13888 * @return {number} 13889 */ 13890 Astro._newMoonAtOrAfter = function (jd) { 13891 var phase = Astro._lunarSolarAngle(jd); 13892 // 11.450086114414322 is the julian day of the 0th full moon 13893 // 29.530588853000001 is the average length of a month 13894 var guess = Math.round((jd - 11.450086114414322 - RataDie.gregorianEpoch) / 29.530588853000001 - phase / 360); 13895 var current; 13896 while ((current = Astro._newMoonTime(guess)) < jd) { 13897 guess++; 13898 } 13899 return current; 13900 }; 13901 13902 /** 13903 * @static 13904 * @protected 13905 * @param {number} jd JD to calculate from 13906 * @param {number} longitude longitude to seek 13907 * @returns {number} the JD of the next time that the solar longitude 13908 * is a multiple of the given longitude 13909 */ 13910 Astro._nextSolarLongitude = function(jd, longitude) { 13911 var rate = 365.242189 / 360.0; 13912 var tau = jd + rate * Astro._fixangle(longitude - Astro._solarLongitude(jd)); 13913 var start = Math.max(jd, tau - 5.0); 13914 var end = tau + 5.0; 13915 13916 return SearchUtils.bisectionSearch(0, start, end, 1e-6, function (l) { 13917 return 180 - Astro._fixangle(Astro._solarLongitude(l) - longitude); 13918 }); 13919 }; 13920 13921 /** 13922 * Floor the julian day to midnight of the current julian day. 13923 * 13924 * @static 13925 * @protected 13926 * @param {number} jd the julian to round 13927 * @return {number} the jd floored to the midnight of the julian day 13928 */ 13929 Astro._floorToJD = function(jd) { 13930 return Math.floor(jd - 0.5) + 0.5; 13931 }; 13932 13933 /** 13934 * Floor the julian day to midnight of the current julian day. 13935 * 13936 * @static 13937 * @protected 13938 * @param {number} jd the julian to round 13939 * @return {number} the jd floored to the midnight of the julian day 13940 */ 13941 Astro._ceilToJD = function(jd) { 13942 return Math.ceil(jd + 0.5) - 0.5; 13943 }; 13944 13945 13946 13947 /*< PersRataDie.js */ 13948 /* 13949 * persratadie.js - Represent a rata die date in the Persian calendar 13950 * 13951 * Copyright © 2014-2015, JEDLSoft 13952 * 13953 * Licensed under the Apache License, Version 2.0 (the "License"); 13954 * you may not use this file except in compliance with the License. 13955 * You may obtain a copy of the License at 13956 * 13957 * http://www.apache.org/licenses/LICENSE-2.0 13958 * 13959 * Unless required by applicable law or agreed to in writing, software 13960 * distributed under the License is distributed on an "AS IS" BASIS, 13961 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13962 * 13963 * See the License for the specific language governing permissions and 13964 * limitations under the License. 13965 */ 13966 13967 /* !depends 13968 ilib.js 13969 MathUtils.js 13970 RataDie.js 13971 Astro.js 13972 GregorianDate.js 13973 */ 13974 13975 13976 13977 13978 /** 13979 * @class 13980 * Construct a new Persian RD date number object. The constructor parameters can 13981 * contain any of the following properties: 13982 * 13983 * <ul> 13984 * <li><i>unixtime<i> - sets the time of this instance according to the given 13985 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 13986 * 13987 * <li><i>julianday</i> - sets the time of this instance according to the given 13988 * Julian Day instance or the Julian Day given as a float 13989 * 13990 * <li><i>year</i> - any integer, including 0 13991 * 13992 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 13993 * 13994 * <li><i>day</i> - 1 to 31 13995 * 13996 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 13997 * is always done with an unambiguous 24 hour representation 13998 * 13999 * <li><i>minute</i> - 0 to 59 14000 * 14001 * <li><i>second</i> - 0 to 59 14002 * 14003 * <li><i>millisecond</i> - 0 to 999 14004 * 14005 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14006 * </ul> 14007 * 14008 * If the constructor is called with another Persian date instance instead of 14009 * a parameter block, the other instance acts as a parameter block and its 14010 * settings are copied into the current instance.<p> 14011 * 14012 * If the constructor is called with no arguments at all or if none of the 14013 * properties listed above are present, then the RD is calculate based on 14014 * the current date at the time of instantiation. <p> 14015 * 14016 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14017 * specified in the params, it is assumed that they have the smallest possible 14018 * value in the range for the property (zero or one).<p> 14019 * 14020 * 14021 * @private 14022 * @constructor 14023 * @extends RataDie 14024 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 14025 */ 14026 var PersRataDie = function(params) { 14027 this.rd = NaN; 14028 Astro.initAstro( 14029 params && typeof(params.sync) === 'boolean' ? params.sync : true, 14030 params && params.loadParams, 14031 ilib.bind(this, function (x) { 14032 RataDie.call(this, params); 14033 if (params && typeof(params.callback) === 'function') { 14034 params.callback(this); 14035 } 14036 }) 14037 ); 14038 }; 14039 14040 PersRataDie.prototype = new RataDie(); 14041 PersRataDie.prototype.parent = RataDie; 14042 PersRataDie.prototype.constructor = PersRataDie; 14043 14044 /** 14045 * The difference between a zero Julian day and the first Persian date 14046 * @private 14047 * @type number 14048 */ 14049 PersRataDie.prototype.epoch = 1948319.5; 14050 14051 /** 14052 * @protected 14053 */ 14054 PersRataDie.prototype._tehranEquinox = function(year) { 14055 var equJED, equJD, equAPP, equTehran, dtTehran, eot; 14056 14057 // March equinox in dynamical time 14058 equJED = Astro._equinox(year, 0); 14059 14060 // Correct for delta T to obtain Universal time 14061 equJD = equJED - (Astro._deltat(year) / (24 * 60 * 60)); 14062 14063 // Apply the equation of time to yield the apparent time at Greenwich 14064 eot = Astro._equationOfTime(equJED) * 360; 14065 eot = (eot - 20 * Math.floor(eot/20)) / 360; 14066 equAPP = equJD + eot; 14067 14068 /* 14069 * Finally, we must correct for the constant difference between 14070 * the Greenwich meridian and the time zone standard for Iran 14071 * Standard time, 52 degrees 30 minutes to the East. 14072 */ 14073 14074 dtTehran = 52.5 / 360; 14075 equTehran = equAPP + dtTehran; 14076 14077 return equTehran; 14078 }; 14079 14080 /** 14081 * Calculate the year based on the given Julian day. 14082 * @protected 14083 * @param {number} jd the Julian day to get the year for 14084 * @return {{year:number,equinox:number}} the year and the last equinox 14085 */ 14086 PersRataDie.prototype._getYear = function(jd) { 14087 var gd = new GregorianDate({julianday: jd}); 14088 var guess = gd.getYears() - 2, 14089 nexteq, 14090 ret = {}; 14091 14092 //ret.equinox = Math.floor(this._tehranEquinox(guess)); 14093 ret.equinox = this._tehranEquinox(guess); 14094 while (ret.equinox > jd) { 14095 guess--; 14096 // ret.equinox = Math.floor(this._tehranEquinox(guess)); 14097 ret.equinox = this._tehranEquinox(guess); 14098 } 14099 nexteq = ret.equinox - 1; 14100 // if the equinox falls after noon, then the day after that is the start of the 14101 // next year, so truncate the JD to get the noon of the day before the day with 14102 //the equinox on it, then add 0.5 to get the midnight of that day 14103 while (!(Math.floor(ret.equinox) + 0.5 <= jd && jd < Math.floor(nexteq) + 0.5)) { 14104 ret.equinox = nexteq; 14105 guess++; 14106 // nexteq = Math.floor(this._tehranEquinox(guess)); 14107 nexteq = this._tehranEquinox(guess); 14108 } 14109 14110 // Mean solar tropical year is 365.24219878 days 14111 ret.year = Math.round((ret.equinox - this.epoch - 1) / 365.24219878) + 1; 14112 14113 return ret; 14114 }; 14115 14116 /** 14117 * Calculate the Rata Die (fixed day) number of the given date from the 14118 * date components. 14119 * 14120 * @protected 14121 * @param {Object} date the date components to calculate the RD from 14122 */ 14123 PersRataDie.prototype._setDateComponents = function(date) { 14124 var adr, guess, jd; 14125 14126 // Mean solar tropical year is 365.24219878 days 14127 guess = this.epoch + 1 + 365.24219878 * ((date.year || 0) - 2); 14128 adr = {year: (date.year || 0) - 1, equinox: 0}; 14129 14130 while (adr.year < date.year) { 14131 adr = this._getYear(guess); 14132 guess = adr.equinox + (365.24219878 + 2); 14133 } 14134 14135 jd = Math.floor(adr.equinox) + 14136 (((date.month || 0) <= 7) ? 14137 (((date.month || 1) - 1) * 31) : 14138 ((((date.month || 1) - 1) * 30) + 6) 14139 ) + 14140 ((date.day || 1) - 1 + 0.5); // add 0.5 so that we convert JDs, which start at noon to RDs which start at midnight 14141 14142 jd += ((date.hour || 0) * 3600000 + 14143 (date.minute || 0) * 60000 + 14144 (date.second || 0) * 1000 + 14145 (date.millisecond || 0)) / 14146 86400000; 14147 14148 this.rd = jd - this.epoch; 14149 }; 14150 14151 /** 14152 * Return the rd number of the particular day of the week on or before the 14153 * given rd. eg. The Sunday on or before the given rd. 14154 * @private 14155 * @param {number} rd the rata die date of the reference date 14156 * @param {number} dayOfWeek the day of the week that is being sought relative 14157 * to the current date 14158 * @return {number} the rd of the day of the week 14159 */ 14160 PersRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 14161 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 14162 }; 14163 14164 14165 /*< PersianCal.js */ 14166 /* 14167 * PersianCal.js - Represent a Persian astronomical (Hijjri) calendar object. 14168 * 14169 * Copyright © 2014-2015,2018, JEDLSoft 14170 * 14171 * Licensed under the Apache License, Version 2.0 (the "License"); 14172 * you may not use this file except in compliance with the License. 14173 * You may obtain a copy of the License at 14174 * 14175 * http://www.apache.org/licenses/LICENSE-2.0 14176 * 14177 * Unless required by applicable law or agreed to in writing, software 14178 * distributed under the License is distributed on an "AS IS" BASIS, 14179 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14180 * 14181 * See the License for the specific language governing permissions and 14182 * limitations under the License. 14183 */ 14184 14185 14186 /* !depends 14187 Calendar.js 14188 PersRataDie.js 14189 */ 14190 14191 14192 14193 /** 14194 * @class 14195 * Construct a new Persian astronomical (Hijjri) calendar object. This class encodes 14196 * information about a Persian calendar. This class differs from the 14197 * Persian calendar in that the leap years are calculated based on the 14198 * astronomical observations of the sun in Teheran, instead of calculating 14199 * the leap years based on a regular cyclical rhythm algorithm.<p> 14200 * 14201 * @param {Object=} options Options governing the construction of this instance 14202 * @constructor 14203 * @extends Calendar 14204 */ 14205 var PersianCal = function(options) { 14206 this.type = "persian"; 14207 14208 if (options && typeof(options.onLoad) === "function") { 14209 options.onLoad(this); 14210 } 14211 }; 14212 14213 /** 14214 * @private 14215 * @const 14216 * @type Array.<number> 14217 * the lengths of each month 14218 */ 14219 PersianCal.monthLengths = [ 14220 31, // Farvardin 14221 31, // Ordibehesht 14222 31, // Khordad 14223 31, // Tir 14224 31, // Mordad 14225 31, // Shahrivar 14226 30, // Mehr 14227 30, // Aban 14228 30, // Azar 14229 30, // Dey 14230 30, // Bahman 14231 29 // Esfand 14232 ]; 14233 14234 /** 14235 * Return the number of months in the given year. The number of months in a year varies 14236 * for some luni-solar calendars because in some years, an extra month is needed to extend the 14237 * days in a year to an entire solar year. The month is represented as a 1-based number 14238 * where 1=first month, 2=second month, etc. 14239 * 14240 * @param {number} year a year for which the number of months is sought 14241 * @return {number} The number of months in the given year 14242 */ 14243 PersianCal.prototype.getNumMonths = function(year) { 14244 return 12; 14245 }; 14246 14247 /** 14248 * Return the number of days in a particular month in a particular year. This function 14249 * can return a different number for a month depending on the year because of things 14250 * like leap years. 14251 * 14252 * @param {number} month the month for which the length is sought 14253 * @param {number} year the year within which that month can be found 14254 * @return {number} the number of days within the given month in the given year 14255 */ 14256 PersianCal.prototype.getMonLength = function(month, year) { 14257 if (month !== 12 || !this.isLeapYear(year)) { 14258 return PersianCal.monthLengths[month-1]; 14259 } else { 14260 // Month 12, Esfand, has 30 days instead of 29 in leap years 14261 return 30; 14262 } 14263 }; 14264 14265 /** 14266 * Return true if the given year is a leap year in the Persian astronomical calendar. 14267 * @param {number} year the year for which the leap year information is being sought 14268 * @return {boolean} true if the given year is a leap year 14269 */ 14270 PersianCal.prototype.isLeapYear = function(year) { 14271 var rdNextYear = new PersRataDie({ 14272 cal: this, 14273 year: year + 1, 14274 month: 1, 14275 day: 1, 14276 hour: 0, 14277 minute: 0, 14278 second: 0, 14279 millisecond: 0 14280 }); 14281 var rdThisYear = new PersRataDie({ 14282 cal: this, 14283 year: year, 14284 month: 1, 14285 day: 1, 14286 hour: 0, 14287 minute: 0, 14288 second: 0, 14289 millisecond: 0 14290 }); 14291 return (rdNextYear.getRataDie() - rdThisYear.getRataDie()) > 365; 14292 }; 14293 14294 /** 14295 * Return the type of this calendar. 14296 * 14297 * @return {string} the name of the type of this calendar 14298 */ 14299 PersianCal.prototype.getType = function() { 14300 return this.type; 14301 }; 14302 14303 /* register this calendar for the factory method */ 14304 Calendar._constructors["persian"] = PersianCal; 14305 14306 14307 14308 /*< PersianDate.js */ 14309 /* 14310 * PersianDate.js - Represent a date in the Persian astronomical (Hijjri) calendar 14311 * 14312 * Copyright © 2014-2015, JEDLSoft 14313 * 14314 * Licensed under the Apache License, Version 2.0 (the "License"); 14315 * you may not use this file except in compliance with the License. 14316 * You may obtain a copy of the License at 14317 * 14318 * http://www.apache.org/licenses/LICENSE-2.0 14319 * 14320 * Unless required by applicable law or agreed to in writing, software 14321 * distributed under the License is distributed on an "AS IS" BASIS, 14322 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14323 * 14324 * See the License for the specific language governing permissions and 14325 * limitations under the License. 14326 */ 14327 14328 /* !depends 14329 ilib.js 14330 Locale.js 14331 TimeZone.js 14332 IDate.js 14333 PersRataDie.js 14334 PersianCal.js 14335 SearchUtils.js 14336 MathUtils.js 14337 JSUtils.js 14338 LocaleInfo.js 14339 Astro.js 14340 */ 14341 14342 // !data astro 14343 14344 14345 14346 /** 14347 * @class 14348 * 14349 * Construct a new Persian astronomical date object. The constructor parameters can 14350 * contain any of the following properties: 14351 * 14352 * <ul> 14353 * <li><i>unixtime<i> - sets the time of this instance according to the given 14354 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14355 * 14356 * <li><i>julianday</i> - sets the time of this instance according to the given 14357 * Julian Day instance or the Julian Day given as a float 14358 * 14359 * <li><i>year</i> - any integer, including 0 14360 * 14361 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14362 * 14363 * <li><i>day</i> - 1 to 31 14364 * 14365 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14366 * is always done with an unambiguous 24 hour representation 14367 * 14368 * <li><i>minute</i> - 0 to 59 14369 * 14370 * <li><i>second</i> - 0 to 59 14371 * 14372 * <li><i>millisecond</i> - 0 to 999 14373 * 14374 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 14375 * of this persian date. The date/time is kept in the local time. The time zone 14376 * is used later if this date is formatted according to a different time zone and 14377 * the difference has to be calculated, or when the date format has a time zone 14378 * component in it. 14379 * 14380 * <li><i>locale</i> - locale for this persian date. If the time zone is not 14381 * given, it can be inferred from this locale. For locales that span multiple 14382 * time zones, the one with the largest population is chosen as the one that 14383 * represents the locale. 14384 * 14385 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14386 * </ul> 14387 * 14388 * If the constructor is called with another Persian date instance instead of 14389 * a parameter block, the other instance acts as a parameter block and its 14390 * settings are copied into the current instance.<p> 14391 * 14392 * If the constructor is called with no arguments at all or if none of the 14393 * properties listed above 14394 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 14395 * components are 14396 * filled in with the current date at the time of instantiation. Note that if 14397 * you do not give the time zone when defaulting to the current time and the 14398 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 14399 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 14400 * Mean Time").<p> 14401 * 14402 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14403 * specified in the params, it is assumed that they have the smallest possible 14404 * value in the range for the property (zero or one).<p> 14405 * 14406 * 14407 * @constructor 14408 * @extends IDate 14409 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 14410 */ 14411 var PersianDate = function(params) { 14412 this.cal = new PersianCal(); 14413 14414 params = params || {}; 14415 14416 if (params.timezone) { 14417 this.timezone = params.timezone; 14418 } 14419 if (params.locale) { 14420 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 14421 } 14422 14423 if (!this.timezone) { 14424 if (this.locale) { 14425 new LocaleInfo(this.locale, { 14426 sync: params.sync, 14427 loadParams: params.loadParams, 14428 onLoad: ilib.bind(this, function(li) { 14429 this.li = li; 14430 this.timezone = li.getTimeZone(); 14431 this._init(params); 14432 }) 14433 }); 14434 } else { 14435 this.timezone = "local"; 14436 this._init(params); 14437 } 14438 } else { 14439 this._init(params); 14440 } 14441 }; 14442 14443 PersianDate.prototype = new IDate({noinstance: true}); 14444 PersianDate.prototype.parent = IDate; 14445 PersianDate.prototype.constructor = PersianDate; 14446 14447 /** 14448 * @private 14449 * Initialize this date object 14450 */ 14451 PersianDate.prototype._init = function (params) { 14452 Astro.initAstro( 14453 typeof(params.sync) === 'boolean' ? params.sync : true, 14454 params.loadParams, 14455 ilib.bind(this, function (x) { 14456 if (params.year || params.month || params.day || params.hour || 14457 params.minute || params.second || params.millisecond) { 14458 /** 14459 * Year in the Persian calendar. 14460 * @type number 14461 */ 14462 this.year = parseInt(params.year, 10) || 0; 14463 14464 /** 14465 * The month number, ranging from 1 to 12 14466 * @type number 14467 */ 14468 this.month = parseInt(params.month, 10) || 1; 14469 14470 /** 14471 * The day of the month. This ranges from 1 to 31. 14472 * @type number 14473 */ 14474 this.day = parseInt(params.day, 10) || 1; 14475 14476 /** 14477 * The hour of the day. This can be a number from 0 to 23, as times are 14478 * stored unambiguously in the 24-hour clock. 14479 * @type number 14480 */ 14481 this.hour = parseInt(params.hour, 10) || 0; 14482 14483 /** 14484 * The minute of the hours. Ranges from 0 to 59. 14485 * @type number 14486 */ 14487 this.minute = parseInt(params.minute, 10) || 0; 14488 14489 /** 14490 * The second of the minute. Ranges from 0 to 59. 14491 * @type number 14492 */ 14493 this.second = parseInt(params.second, 10) || 0; 14494 14495 /** 14496 * The millisecond of the second. Ranges from 0 to 999. 14497 * @type number 14498 */ 14499 this.millisecond = parseInt(params.millisecond, 10) || 0; 14500 14501 /** 14502 * The day of the year. Ranges from 1 to 366. 14503 * @type number 14504 */ 14505 this.dayOfYear = parseInt(params.dayOfYear, 10); 14506 14507 if (typeof(params.dst) === 'boolean') { 14508 this.dst = params.dst; 14509 } 14510 14511 this.newRd(JSUtils.merge(params, { 14512 callback: ilib.bind(this, function(rd) { 14513 this.rd = rd; 14514 14515 new TimeZone({ 14516 id: this.timezone, 14517 sync: params.sync, 14518 loadParams: params.loadParams, 14519 onLoad: ilib.bind(this, function(tz) { 14520 this.tz = tz; 14521 // add the time zone offset to the rd to convert to UTC 14522 // getOffsetMillis requires that this.year, this.rd, and this.dst 14523 // are set in order to figure out which time zone rules apply and 14524 // what the offset is at that point in the year 14525 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 14526 if (this.offset !== 0) { 14527 this.rd = this.newRd({ 14528 rd: this.rd.getRataDie() - this.offset 14529 }); 14530 } 14531 this._init2(params); 14532 }) 14533 }); 14534 }) 14535 })); 14536 14537 } else { 14538 this._init2(params); 14539 } 14540 }) 14541 ); 14542 }; 14543 14544 /** 14545 * @private 14546 * Finish initializing this date object 14547 */ 14548 PersianDate.prototype._init2 = function (params) { 14549 if (!this.rd) { 14550 this.newRd(JSUtils.merge(params, { 14551 callback: ilib.bind(this, function(rd) { 14552 this.rd = rd; 14553 this._calcDateComponents(); 14554 14555 if (typeof(params.onLoad) === "function") { 14556 params.onLoad(this); 14557 } 14558 }) 14559 })); 14560 } else { 14561 if (typeof(params.onLoad) === "function") { 14562 params.onLoad(this); 14563 } 14564 } 14565 }; 14566 14567 /** 14568 * @private 14569 * @const 14570 * @type Array.<number> 14571 * the cumulative lengths of each month, for a non-leap year 14572 */ 14573 PersianDate.cumMonthLengths = [ 14574 0, // Farvardin 14575 31, // Ordibehesht 14576 62, // Khordad 14577 93, // Tir 14578 124, // Mordad 14579 155, // Shahrivar 14580 186, // Mehr 14581 216, // Aban 14582 246, // Azar 14583 276, // Dey 14584 306, // Bahman 14585 336, // Esfand 14586 366 14587 ]; 14588 14589 /** 14590 * Return a new RD for this date type using the given params. 14591 * @protected 14592 * @param {Object=} params the parameters used to create this rata die instance 14593 * @returns {RataDie} the new RD instance for the given params 14594 */ 14595 PersianDate.prototype.newRd = function (params) { 14596 return new PersRataDie(params); 14597 }; 14598 14599 /** 14600 * Return the year for the given RD 14601 * @protected 14602 * @param {number} rd RD to calculate from 14603 * @returns {number} the year for the RD 14604 */ 14605 PersianDate.prototype._calcYear = function(rd) { 14606 var julianday = rd + this.rd.epoch; 14607 return this.rd._getYear(julianday).year; 14608 }; 14609 14610 /** 14611 * @private 14612 * Calculate date components for the given RD date. 14613 */ 14614 PersianDate.prototype._calcDateComponents = function () { 14615 var remainder, 14616 rd = this.rd.getRataDie(); 14617 14618 this.year = this._calcYear(rd); 14619 14620 if (typeof(this.offset) === "undefined") { 14621 // now offset the RD by the time zone, then recalculate in case we were 14622 // near the year boundary 14623 if (!this.tz) { 14624 this.tz = new TimeZone({id: this.timezone}); 14625 } 14626 this.offset = this.tz.getOffsetMillis(this) / 86400000; 14627 } 14628 14629 if (this.offset !== 0) { 14630 rd += this.offset; 14631 this.year = this._calcYear(rd); 14632 } 14633 14634 //console.log("PersDate.calcComponent: calculating for rd " + rd); 14635 //console.log("PersDate.calcComponent: year is " + ret.year); 14636 var yearStart = this.newRd({ 14637 year: this.year, 14638 month: 1, 14639 day: 1, 14640 hour: 0, 14641 minute: 0, 14642 second: 0, 14643 millisecond: 0 14644 }); 14645 remainder = rd - yearStart.getRataDie() + 1; 14646 14647 this.dayOfYear = remainder; 14648 14649 //console.log("PersDate.calcComponent: remainder is " + remainder); 14650 14651 this.month = SearchUtils.bsearch(Math.floor(remainder), PersianDate.cumMonthLengths); 14652 remainder -= PersianDate.cumMonthLengths[this.month-1]; 14653 14654 //console.log("PersDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 14655 14656 this.day = Math.floor(remainder); 14657 remainder -= this.day; 14658 14659 //console.log("PersDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 14660 14661 // now convert to milliseconds for the rest of the calculation 14662 remainder = Math.round(remainder * 86400000); 14663 14664 this.hour = Math.floor(remainder/3600000); 14665 remainder -= this.hour * 3600000; 14666 14667 this.minute = Math.floor(remainder/60000); 14668 remainder -= this.minute * 60000; 14669 14670 this.second = Math.floor(remainder/1000); 14671 remainder -= this.second * 1000; 14672 14673 this.millisecond = remainder; 14674 }; 14675 14676 /** 14677 * Return the day of the week of this date. The day of the week is encoded 14678 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 14679 * 14680 * @return {number} the day of the week 14681 */ 14682 PersianDate.prototype.getDayOfWeek = function() { 14683 var rd = Math.floor(this.getRataDie()); 14684 return MathUtils.mod(rd-3, 7); 14685 }; 14686 14687 /** 14688 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 14689 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 14690 * December 31st is 365 in regular years, or 366 in leap years. 14691 * @return {number} the ordinal day of the year 14692 */ 14693 PersianDate.prototype.getDayOfYear = function() { 14694 return PersianDate.cumMonthLengths[this.month-1] + this.day; 14695 }; 14696 14697 /** 14698 * Return the era for this date as a number. The value for the era for Persian 14699 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 14700 * persico or AP). 14701 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 14702 * there is a year 0, so any years that are negative or zero are BP. 14703 * @return {number} 1 if this date is in the common era, -1 if it is before the 14704 * common era 14705 */ 14706 PersianDate.prototype.getEra = function() { 14707 return (this.year < 1) ? -1 : 1; 14708 }; 14709 14710 /** 14711 * Return the name of the calendar that governs this date. 14712 * 14713 * @return {string} a string giving the name of the calendar 14714 */ 14715 PersianDate.prototype.getCalendar = function() { 14716 return "persian"; 14717 }; 14718 14719 // register with the factory method 14720 IDate._constructors["persian"] = PersianDate; 14721 14722 14723 /*< PersianAlgoCal.js */ 14724 /* 14725 * PersianAlgoCal.js - Represent a Persian algorithmic calendar object. 14726 * 14727 * Copyright © 2014-2015,2018, JEDLSoft 14728 * 14729 * Licensed under the Apache License, Version 2.0 (the "License"); 14730 * you may not use this file except in compliance with the License. 14731 * You may obtain a copy of the License at 14732 * 14733 * http://www.apache.org/licenses/LICENSE-2.0 14734 * 14735 * Unless required by applicable law or agreed to in writing, software 14736 * distributed under the License is distributed on an "AS IS" BASIS, 14737 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14738 * 14739 * See the License for the specific language governing permissions and 14740 * limitations under the License. 14741 */ 14742 14743 14744 /* !depends Calendar.js MathUtils.js */ 14745 14746 14747 /** 14748 * @class 14749 * Construct a new Persian algorithmic calendar object. This class encodes information about 14750 * a Persian algorithmic calendar.<p> 14751 * 14752 * @param {Object=} options Options governing the construction of this instance 14753 * @constructor 14754 * @extends Calendar 14755 */ 14756 var PersianAlgoCal = function(options) { 14757 this.type = "persian-algo"; 14758 14759 if (options && typeof(options.onLoad) === "function") { 14760 options.onLoad(this); 14761 } 14762 }; 14763 14764 /** 14765 * @private 14766 * @const 14767 * @type Array.<number> 14768 * the lengths of each month 14769 */ 14770 PersianAlgoCal.monthLengths = [ 14771 31, // Farvardin 14772 31, // Ordibehesht 14773 31, // Khordad 14774 31, // Tir 14775 31, // Mordad 14776 31, // Shahrivar 14777 30, // Mehr 14778 30, // Aban 14779 30, // Azar 14780 30, // Dey 14781 30, // Bahman 14782 29 // Esfand 14783 ]; 14784 14785 /** 14786 * Return the number of months in the given year. The number of months in a year varies 14787 * for some luni-solar calendars because in some years, an extra month is needed to extend the 14788 * days in a year to an entire solar year. The month is represented as a 1-based number 14789 * where 1=first month, 2=second month, etc. 14790 * 14791 * @param {number} year a year for which the number of months is sought 14792 * @return {number} The number of months in the given year 14793 */ 14794 PersianAlgoCal.prototype.getNumMonths = function(year) { 14795 return 12; 14796 }; 14797 14798 /** 14799 * Return the number of days in a particular month in a particular year. This function 14800 * can return a different number for a month depending on the year because of things 14801 * like leap years. 14802 * 14803 * @param {number} month the month for which the length is sought 14804 * @param {number} year the year within which that month can be found 14805 * @return {number} the number of days within the given month in the given year 14806 */ 14807 PersianAlgoCal.prototype.getMonLength = function(month, year) { 14808 if (month !== 12 || !this.isLeapYear(year)) { 14809 return PersianAlgoCal.monthLengths[month-1]; 14810 } else { 14811 // Month 12, Esfand, has 30 days instead of 29 in leap years 14812 return 30; 14813 } 14814 }; 14815 14816 /** 14817 * Return the equivalent year in the 2820 year cycle that begins on 14818 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 14819 * whereas the others do not specifically. This cycle can be used as 14820 * a proxy for other years outside of the cycle by shifting them into 14821 * the cycle. 14822 * @param {number} year year to find the equivalent cycle year for 14823 * @returns {number} the equivalent cycle year 14824 */ 14825 PersianAlgoCal.prototype.equivalentCycleYear = function(year) { 14826 var y = year - (year >= 0 ? 474 : 473); 14827 return MathUtils.mod(y, 2820) + 474; 14828 }; 14829 14830 /** 14831 * Return true if the given year is a leap year in the Persian calendar. 14832 * The year parameter may be given as a number, or as a PersAlgoDate object. 14833 * @param {number} year the year for which the leap year information is being sought 14834 * @return {boolean} true if the given year is a leap year 14835 */ 14836 PersianAlgoCal.prototype.isLeapYear = function(year) { 14837 return (MathUtils.mod((this.equivalentCycleYear(year) + 38) * 682, 2816) < 682); 14838 }; 14839 14840 /** 14841 * Return the type of this calendar. 14842 * 14843 * @return {string} the name of the type of this calendar 14844 */ 14845 PersianAlgoCal.prototype.getType = function() { 14846 return this.type; 14847 }; 14848 14849 14850 /* register this calendar for the factory method */ 14851 Calendar._constructors["persian-algo"] = PersianAlgoCal; 14852 14853 14854 14855 /*< PersAlgoRataDie.js */ 14856 /* 14857 * PersAlsoRataDie.js - Represent an RD date in the Persian algorithmic calendar 14858 * 14859 * Copyright © 2014-2015, JEDLSoft 14860 * 14861 * Licensed under the Apache License, Version 2.0 (the "License"); 14862 * you may not use this file except in compliance with the License. 14863 * You may obtain a copy of the License at 14864 * 14865 * http://www.apache.org/licenses/LICENSE-2.0 14866 * 14867 * Unless required by applicable law or agreed to in writing, software 14868 * distributed under the License is distributed on an "AS IS" BASIS, 14869 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14870 * 14871 * See the License for the specific language governing permissions and 14872 * limitations under the License. 14873 */ 14874 14875 /* !depends 14876 PersianAlgoCal.js 14877 MathUtils.js 14878 RataDie.js 14879 */ 14880 14881 14882 /** 14883 * @class 14884 * Construct a new Persian RD date number object. The constructor parameters can 14885 * contain any of the following properties: 14886 * 14887 * <ul> 14888 * <li><i>unixtime<i> - sets the time of this instance according to the given 14889 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 14890 * 14891 * <li><i>julianday</i> - sets the time of this instance according to the given 14892 * Julian Day instance or the Julian Day given as a float 14893 * 14894 * <li><i>year</i> - any integer, including 0 14895 * 14896 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 14897 * 14898 * <li><i>day</i> - 1 to 31 14899 * 14900 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 14901 * is always done with an unambiguous 24 hour representation 14902 * 14903 * <li><i>minute</i> - 0 to 59 14904 * 14905 * <li><i>second</i> - 0 to 59 14906 * 14907 * <li><i>millisecond</i> - 0 to 999 14908 * 14909 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 14910 * </ul> 14911 * 14912 * If the constructor is called with another Persian date instance instead of 14913 * a parameter block, the other instance acts as a parameter block and its 14914 * settings are copied into the current instance.<p> 14915 * 14916 * If the constructor is called with no arguments at all or if none of the 14917 * properties listed above are present, then the RD is calculate based on 14918 * the current date at the time of instantiation. <p> 14919 * 14920 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 14921 * specified in the params, it is assumed that they have the smallest possible 14922 * value in the range for the property (zero or one).<p> 14923 * 14924 * 14925 * @private 14926 * @constructor 14927 * @extends RataDie 14928 * @param {Object=} params parameters that govern the settings and behaviour of this Persian RD date 14929 */ 14930 var PersAlgoRataDie = function(params) { 14931 this.cal = params && params.cal || new PersianAlgoCal(); 14932 this.rd = NaN; 14933 RataDie.call(this, params); 14934 }; 14935 14936 PersAlgoRataDie.prototype = new RataDie(); 14937 PersAlgoRataDie.prototype.parent = RataDie; 14938 PersAlgoRataDie.prototype.constructor = PersAlgoRataDie; 14939 14940 /** 14941 * The difference between a zero Julian day and the first Persian date 14942 * @private 14943 * @type number 14944 */ 14945 PersAlgoRataDie.prototype.epoch = 1948319.5; 14946 14947 /** 14948 * @private 14949 * @const 14950 * @type Array.<number> 14951 * the cumulative lengths of each month, for a non-leap year 14952 */ 14953 PersAlgoRataDie.cumMonthLengths = [ 14954 0, // Farvardin 14955 31, // Ordibehesht 14956 62, // Khordad 14957 93, // Tir 14958 124, // Mordad 14959 155, // Shahrivar 14960 186, // Mehr 14961 216, // Aban 14962 246, // Azar 14963 276, // Dey 14964 306, // Bahman 14965 336, // Esfand 14966 365 14967 ]; 14968 14969 /** 14970 * Calculate the Rata Die (fixed day) number of the given date from the 14971 * date components. 14972 * 14973 * @protected 14974 * @param {Object} date the date components to calculate the RD from 14975 */ 14976 PersAlgoRataDie.prototype._setDateComponents = function(date) { 14977 var year = this.cal.equivalentCycleYear(date.year); 14978 var y = date.year - (date.year >= 0 ? 474 : 473); 14979 var rdOfYears = 1029983 * Math.floor(y/2820) + 365 * (year - 1) + Math.floor((682 * year - 110) / 2816); 14980 var dayInYear = (date.month > 1 ? PersAlgoRataDie.cumMonthLengths[date.month-1] : 0) + date.day; 14981 var rdtime = (date.hour * 3600000 + 14982 date.minute * 60000 + 14983 date.second * 1000 + 14984 date.millisecond) / 14985 86400000; 14986 14987 /* 14988 // console.log("getRataDie: converting " + JSON.stringify(this)); 14989 console.log("getRataDie: year is " + year); 14990 console.log("getRataDie: rd of years is " + rdOfYears); 14991 console.log("getRataDie: day in year is " + dayInYear); 14992 console.log("getRataDie: rdtime is " + rdtime); 14993 console.log("getRataDie: rd is " + (rdOfYears + dayInYear + rdtime)); 14994 */ 14995 14996 this.rd = rdOfYears + dayInYear + rdtime; 14997 }; 14998 14999 /** 15000 * Return the rd number of the particular day of the week on or before the 15001 * given rd. eg. The Sunday on or before the given rd. 15002 * @private 15003 * @param {number} rd the rata die date of the reference date 15004 * @param {number} dayOfWeek the day of the week that is being sought relative 15005 * to the current date 15006 * @return {number} the rd of the day of the week 15007 */ 15008 PersAlgoRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 15009 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek - 3, 7); 15010 }; 15011 15012 15013 /*< PersianAlgoDate.js */ 15014 /* 15015 * PersianAlgoDate.js - Represent a date in the Persian algorithmic calendar 15016 * 15017 * Copyright © 2014-2015, JEDLSoft 15018 * 15019 * Licensed under the Apache License, Version 2.0 (the "License"); 15020 * you may not use this file except in compliance with the License. 15021 * You may obtain a copy of the License at 15022 * 15023 * http://www.apache.org/licenses/LICENSE-2.0 15024 * 15025 * Unless required by applicable law or agreed to in writing, software 15026 * distributed under the License is distributed on an "AS IS" BASIS, 15027 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15028 * 15029 * See the License for the specific language governing permissions and 15030 * limitations under the License. 15031 */ 15032 15033 /* !depends 15034 ilib.js 15035 Locale.js 15036 LocaleInfo.js 15037 TimeZone.js 15038 IDate.js 15039 PersianAlgoCal.js 15040 SearchUtils.js 15041 MathUtils.js 15042 PersAlgoRataDie.js 15043 */ 15044 15045 15046 15047 15048 /** 15049 * @class 15050 * 15051 * Construct a new Persian date object. The constructor parameters can 15052 * contain any of the following properties: 15053 * 15054 * <ul> 15055 * <li><i>unixtime<i> - sets the time of this instance according to the given 15056 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15057 * 15058 * <li><i>julianday</i> - sets the time of this instance according to the given 15059 * Julian Day instance or the Julian Day given as a float 15060 * 15061 * <li><i>year</i> - any integer, including 0 15062 * 15063 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15064 * 15065 * <li><i>day</i> - 1 to 31 15066 * 15067 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15068 * is always done with an unambiguous 24 hour representation 15069 * 15070 * <li><i>minute</i> - 0 to 59 15071 * 15072 * <li><i>second</i> - 0 to 59 15073 * 15074 * <li><i>millisecond</i> - 0 to 999 15075 * 15076 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 15077 * of this persian date. The date/time is kept in the local time. The time zone 15078 * is used later if this date is formatted according to a different time zone and 15079 * the difference has to be calculated, or when the date format has a time zone 15080 * component in it. 15081 * 15082 * <li><i>locale</i> - locale for this persian date. If the time zone is not 15083 * given, it can be inferred from this locale. For locales that span multiple 15084 * time zones, the one with the largest population is chosen as the one that 15085 * represents the locale. 15086 * 15087 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15088 * </ul> 15089 * 15090 * If the constructor is called with another Persian date instance instead of 15091 * a parameter block, the other instance acts as a parameter block and its 15092 * settings are copied into the current instance.<p> 15093 * 15094 * If the constructor is called with no arguments at all or if none of the 15095 * properties listed above 15096 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 15097 * components are 15098 * filled in with the current date at the time of instantiation. Note that if 15099 * you do not give the time zone when defaulting to the current time and the 15100 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 15101 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 15102 * Mean Time").<p> 15103 * 15104 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15105 * specified in the params, it is assumed that they have the smallest possible 15106 * value in the range for the property (zero or one).<p> 15107 * 15108 * 15109 * @constructor 15110 * @extends IDate 15111 * @param {Object=} params parameters that govern the settings and behaviour of this Persian date 15112 */ 15113 var PersianAlgoDate = function(params) { 15114 this.cal = new PersianAlgoCal(); 15115 15116 params = params || {}; 15117 15118 if (params.timezone) { 15119 this.timezone = params.timezone; 15120 } 15121 if (params.locale) { 15122 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 15123 } 15124 15125 if (!this.timezone) { 15126 if (this.locale) { 15127 new LocaleInfo(this.locale, { 15128 sync: params.sync, 15129 loadParams: params.loadParams, 15130 onLoad: ilib.bind(this, function(li) { 15131 this.li = li; 15132 this.timezone = li.getTimeZone(); 15133 this._init(params); 15134 }) 15135 }); 15136 } else { 15137 this.timezone = "local"; 15138 this._init(params); 15139 } 15140 } else { 15141 this._init(params); 15142 } 15143 15144 15145 if (!this.rd) { 15146 this.rd = this.newRd(params); 15147 this._calcDateComponents(); 15148 } 15149 }; 15150 15151 PersianAlgoDate.prototype = new IDate({noinstance: true}); 15152 PersianAlgoDate.prototype.parent = IDate; 15153 PersianAlgoDate.prototype.constructor = PersianAlgoDate; 15154 15155 /** 15156 * Initialize this date 15157 * @private 15158 */ 15159 PersianAlgoDate.prototype._init = function (params) { 15160 if (params.year || params.month || params.day || params.hour || 15161 params.minute || params.second || params.millisecond ) { 15162 /** 15163 * Year in the Persian calendar. 15164 * @type number 15165 */ 15166 this.year = parseInt(params.year, 10) || 0; 15167 15168 /** 15169 * The month number, ranging from 1 to 12 15170 * @type number 15171 */ 15172 this.month = parseInt(params.month, 10) || 1; 15173 15174 /** 15175 * The day of the month. This ranges from 1 to 31. 15176 * @type number 15177 */ 15178 this.day = parseInt(params.day, 10) || 1; 15179 15180 /** 15181 * The hour of the day. This can be a number from 0 to 23, as times are 15182 * stored unambiguously in the 24-hour clock. 15183 * @type number 15184 */ 15185 this.hour = parseInt(params.hour, 10) || 0; 15186 15187 /** 15188 * The minute of the hours. Ranges from 0 to 59. 15189 * @type number 15190 */ 15191 this.minute = parseInt(params.minute, 10) || 0; 15192 15193 /** 15194 * The second of the minute. Ranges from 0 to 59. 15195 * @type number 15196 */ 15197 this.second = parseInt(params.second, 10) || 0; 15198 15199 /** 15200 * The millisecond of the second. Ranges from 0 to 999. 15201 * @type number 15202 */ 15203 this.millisecond = parseInt(params.millisecond, 10) || 0; 15204 15205 /** 15206 * The day of the year. Ranges from 1 to 366. 15207 * @type number 15208 */ 15209 this.dayOfYear = parseInt(params.dayOfYear, 10); 15210 15211 if (typeof(params.dst) === 'boolean') { 15212 this.dst = params.dst; 15213 } 15214 15215 this.rd = this.newRd(this); 15216 15217 new TimeZone({ 15218 id: this.timezone, 15219 sync: params.sync, 15220 loadParams: params.loadParams, 15221 onLoad: ilib.bind(this, function(tz) { 15222 this.tz = tz; 15223 // add the time zone offset to the rd to convert to UTC 15224 // getOffsetMillis requires that this.year, this.rd, and this.dst 15225 // are set in order to figure out which time zone rules apply and 15226 // what the offset is at that point in the year 15227 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 15228 if (this.offset !== 0) { 15229 this.rd = this.newRd({ 15230 rd: this.rd.getRataDie() - this.offset 15231 }); 15232 } 15233 this._init2(params); 15234 }) 15235 }); 15236 } else { 15237 this._init2(params); 15238 } 15239 }; 15240 15241 /** 15242 * @private 15243 * Finish initializing this date object 15244 */ 15245 PersianAlgoDate.prototype._init2 = function (params) { 15246 if (!this.rd) { 15247 this.rd = this.newRd(params); 15248 this._calcDateComponents(); 15249 } 15250 15251 if (typeof(params.onLoad) === "function") { 15252 params.onLoad(this); 15253 } 15254 }; 15255 15256 /** 15257 * Return a new RD for this date type using the given params. 15258 * @protected 15259 * @param {Object=} params the parameters used to create this rata die instance 15260 * @returns {RataDie} the new RD instance for the given params 15261 */ 15262 PersianAlgoDate.prototype.newRd = function (params) { 15263 return new PersAlgoRataDie(params); 15264 }; 15265 15266 /** 15267 * Return the year for the given RD 15268 * @protected 15269 * @param {number} rd RD to calculate from 15270 * @returns {number} the year for the RD 15271 */ 15272 PersianAlgoDate.prototype._calcYear = function(rd) { 15273 var shiftedRd = rd - 173126; 15274 var numberOfCycles = Math.floor(shiftedRd / 1029983); 15275 var shiftedDayInCycle = MathUtils.mod(shiftedRd, 1029983); 15276 var yearInCycle = (shiftedDayInCycle === 1029982) ? 2820 : Math.floor((2816 * shiftedDayInCycle + 1031337) / 1028522); 15277 var year = 474 + 2820 * numberOfCycles + yearInCycle; 15278 return (year > 0) ? year : year - 1; 15279 }; 15280 15281 /** 15282 * @private 15283 * Calculate date components for the given RD date. 15284 */ 15285 PersianAlgoDate.prototype._calcDateComponents = function () { 15286 var remainder, 15287 rd = this.rd.getRataDie(); 15288 15289 this.year = this._calcYear(rd); 15290 15291 if (typeof(this.offset) === "undefined") { 15292 // now offset the RD by the time zone, then recalculate in case we were 15293 // near the year boundary 15294 if (!this.tz) { 15295 this.tz = new TimeZone({id: this.timezone}); 15296 } 15297 this.offset = this.tz.getOffsetMillis(this) / 86400000; 15298 } 15299 15300 if (this.offset !== 0) { 15301 rd += this.offset; 15302 this.year = this._calcYear(rd); 15303 } 15304 15305 //console.log("PersAlgoDate.calcComponent: calculating for rd " + rd); 15306 //console.log("PersAlgoDate.calcComponent: year is " + ret.year); 15307 var yearStart = this.newRd({ 15308 year: this.year, 15309 month: 1, 15310 day: 1, 15311 hour: 0, 15312 minute: 0, 15313 second: 0, 15314 millisecond: 0 15315 }); 15316 remainder = rd - yearStart.getRataDie() + 1; 15317 15318 this.dayOfYear = remainder; 15319 15320 //console.log("PersAlgoDate.calcComponent: remainder is " + remainder); 15321 15322 this.month = SearchUtils.bsearch(remainder, PersAlgoRataDie.cumMonthLengths); 15323 remainder -= PersAlgoRataDie.cumMonthLengths[this.month-1]; 15324 15325 //console.log("PersAlgoDate.calcComponent: month is " + this.month + " and remainder is " + remainder); 15326 15327 this.day = Math.floor(remainder); 15328 remainder -= this.day; 15329 15330 //console.log("PersAlgoDate.calcComponent: day is " + this.day + " and remainder is " + remainder); 15331 15332 // now convert to milliseconds for the rest of the calculation 15333 remainder = Math.round(remainder * 86400000); 15334 15335 this.hour = Math.floor(remainder/3600000); 15336 remainder -= this.hour * 3600000; 15337 15338 this.minute = Math.floor(remainder/60000); 15339 remainder -= this.minute * 60000; 15340 15341 this.second = Math.floor(remainder/1000); 15342 remainder -= this.second * 1000; 15343 15344 this.millisecond = remainder; 15345 }; 15346 15347 /** 15348 * Return the day of the week of this date. The day of the week is encoded 15349 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 15350 * 15351 * @return {number} the day of the week 15352 */ 15353 PersianAlgoDate.prototype.getDayOfWeek = function() { 15354 var rd = Math.floor(this.getRataDie()); 15355 return MathUtils.mod(rd-3, 7); 15356 }; 15357 15358 /** 15359 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 15360 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 15361 * December 31st is 365 in regular years, or 366 in leap years. 15362 * @return {number} the ordinal day of the year 15363 */ 15364 PersianAlgoDate.prototype.getDayOfYear = function() { 15365 return PersAlgoRataDie.cumMonthLengths[this.month-1] + this.day; 15366 }; 15367 15368 /** 15369 * Return the era for this date as a number. The value for the era for Persian 15370 * calendars is -1 for "before the persian era" (BP) and 1 for "the persian era" (anno 15371 * persico or AP). 15372 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Persian calendar, 15373 * there is a year 0, so any years that are negative or zero are BP. 15374 * @return {number} 1 if this date is in the common era, -1 if it is before the 15375 * common era 15376 */ 15377 PersianAlgoDate.prototype.getEra = function() { 15378 return (this.year < 1) ? -1 : 1; 15379 }; 15380 15381 /** 15382 * Return the name of the calendar that governs this date. 15383 * 15384 * @return {string} a string giving the name of the calendar 15385 */ 15386 PersianAlgoDate.prototype.getCalendar = function() { 15387 return "persian-algo"; 15388 }; 15389 15390 // register with the factory method 15391 IDate._constructors["persian-algo"] = PersianAlgoDate; 15392 15393 15394 /*< HanCal.js */ 15395 /* 15396 * han.js - Represent a Han Chinese Lunar calendar object. 15397 * 15398 * Copyright © 2014-2015, JEDLSoft 15399 * 15400 * Licensed under the Apache License, Version 2.0 (the "License"); 15401 * you may not use this file except in compliance with the License. 15402 * You may obtain a copy of the License at 15403 * 15404 * http://www.apache.org/licenses/LICENSE-2.0 15405 * 15406 * Unless required by applicable law or agreed to in writing, software 15407 * distributed under the License is distributed on an "AS IS" BASIS, 15408 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15409 * 15410 * See the License for the specific language governing permissions and 15411 * limitations under the License. 15412 */ 15413 15414 /* !depends 15415 ilib.js 15416 Calendar.js 15417 MathUtils.js 15418 Astro.js 15419 GregorianDate.js 15420 GregRataDie.js 15421 RataDie.js 15422 */ 15423 15424 15425 15426 15427 /** 15428 * @class 15429 * Construct a new Han algorithmic calendar object. This class encodes information about 15430 * a Han algorithmic calendar.<p> 15431 * 15432 * 15433 * @constructor 15434 * @param {Object=} params optional parameters to load the calendrical data 15435 * @extends Calendar 15436 */ 15437 var HanCal = function(params) { 15438 this.type = "han"; 15439 var sync = params && typeof(params.sync) === 'boolean' ? params.sync : true; 15440 15441 Astro.initAstro(sync, params && params.loadParams, ilib.bind(this, function (x) { 15442 if (params && typeof(params.onLoad) === 'function') { 15443 params.onLoad(this); 15444 } 15445 })); 15446 }; 15447 15448 /** 15449 * @protected 15450 * @static 15451 * @param {number} year 15452 * @param {number=} cycle 15453 * @return {number} 15454 */ 15455 HanCal._getElapsedYear = function(year, cycle) { 15456 var elapsedYear = year || 0; 15457 if (typeof(year) !== 'undefined' && year < 61 && typeof(cycle) !== 'undefined') { 15458 elapsedYear = 60 * cycle + year; 15459 } 15460 return elapsedYear; 15461 }; 15462 15463 /** 15464 * @protected 15465 * @static 15466 * @param {number} jd julian day to calculate from 15467 * @param {number} longitude longitude to seek 15468 * @returns {number} the julian day of the next time that the solar longitude 15469 * is a multiple of the given longitude 15470 */ 15471 HanCal._hanNextSolarLongitude = function(jd, longitude) { 15472 var tz = HanCal._chineseTZ(jd); 15473 var uni = Astro._universalFromLocal(jd, tz); 15474 var sol = Astro._nextSolarLongitude(uni, longitude); 15475 return Astro._localFromUniversal(sol, tz); 15476 }; 15477 15478 /** 15479 * @protected 15480 * @static 15481 * @param {number} jd julian day to calculate from 15482 * @returns {number} the major solar term for the julian day 15483 */ 15484 HanCal._majorSTOnOrAfter = function(jd) { 15485 var tz = HanCal._chineseTZ(jd); 15486 var uni = Astro._universalFromLocal(jd, tz); 15487 var next = Astro._fixangle(30 * Math.ceil(Astro._solarLongitude(uni)/30)); 15488 return HanCal._hanNextSolarLongitude(jd, next); 15489 }; 15490 15491 /** 15492 * @protected 15493 * @static 15494 * @param {number} year the year for which the leap year information is being sought 15495 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15496 * cycle is not given, then the year should be given as elapsed years since the beginning 15497 * of the epoch 15498 */ 15499 HanCal._solsticeBefore = function (year, cycle) { 15500 var elapsedYear = HanCal._getElapsedYear(year, cycle); 15501 var gregyear = elapsedYear - 2697; 15502 var rd = new GregRataDie({ 15503 year: gregyear-1, 15504 month: 12, 15505 day: 15, 15506 hour: 0, 15507 minute: 0, 15508 second: 0, 15509 millisecond: 0 15510 }); 15511 return HanCal._majorSTOnOrAfter(rd.getRataDie() + RataDie.gregorianEpoch); 15512 }; 15513 15514 /** 15515 * @protected 15516 * @static 15517 * @param {number} jd julian day to calculate from 15518 * @returns {number} the current major solar term 15519 */ 15520 HanCal._chineseTZ = function(jd) { 15521 var year = GregorianDate._calcYear(jd - RataDie.gregorianEpoch); 15522 return year < 1929 ? 465.6666666666666666 : 480; 15523 }; 15524 15525 /** 15526 * @protected 15527 * @static 15528 * @param {number} jd julian day to calculate from 15529 * @returns {number} the julian day of next new moon on or after the given julian day date 15530 */ 15531 HanCal._newMoonOnOrAfter = function(jd) { 15532 var tz = HanCal._chineseTZ(jd); 15533 var uni = Astro._universalFromLocal(jd, tz); 15534 var moon = Astro._newMoonAtOrAfter(uni); 15535 // floor to the start of the julian day 15536 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 15537 }; 15538 15539 /** 15540 * @protected 15541 * @static 15542 * @param {number} jd julian day to calculate from 15543 * @returns {number} the julian day of previous new moon before the given julian day date 15544 */ 15545 HanCal._newMoonBefore = function(jd) { 15546 var tz = HanCal._chineseTZ(jd); 15547 var uni = Astro._universalFromLocal(jd, tz); 15548 var moon = Astro._newMoonBefore(uni); 15549 // floor to the start of the julian day 15550 return Astro._floorToJD(Astro._localFromUniversal(moon, tz)); 15551 }; 15552 15553 /** 15554 * @static 15555 * @protected 15556 * @param {number} year the year for which the leap year information is being sought 15557 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15558 * cycle is not given, then the year should be given as elapsed years since the beginning 15559 * of the epoch 15560 */ 15561 HanCal._leapYearCalc = function(year, cycle) { 15562 var ret = { 15563 elapsedYear: HanCal._getElapsedYear(year, cycle) 15564 }; 15565 ret.solstice1 = HanCal._solsticeBefore(ret.elapsedYear); 15566 ret.solstice2 = HanCal._solsticeBefore(ret.elapsedYear+1); 15567 // ceil to the end of the julian day 15568 ret.m1 = HanCal._newMoonOnOrAfter(Astro._ceilToJD(ret.solstice1)); 15569 ret.m2 = HanCal._newMoonBefore(Astro._ceilToJD(ret.solstice2)); 15570 15571 return ret; 15572 }; 15573 15574 /** 15575 * @protected 15576 * @static 15577 * @param {number} jd julian day to calculate from 15578 * @returns {number} the current major solar term 15579 */ 15580 HanCal._currentMajorST = function(jd) { 15581 var s = Astro._solarLongitude(Astro._universalFromLocal(jd, HanCal._chineseTZ(jd))); 15582 return MathUtils.amod(2 + Math.floor(s/30), 12); 15583 }; 15584 15585 /** 15586 * @protected 15587 * @static 15588 * @param {number} jd julian day to calculate from 15589 * @returns {boolean} true if there is no major solar term in the same year 15590 */ 15591 HanCal._noMajorST = function(jd) { 15592 return HanCal._currentMajorST(jd) === HanCal._currentMajorST(HanCal._newMoonOnOrAfter(jd+1)); 15593 }; 15594 15595 /** 15596 * Return the number of months in the given year. The number of months in a year varies 15597 * for some luni-solar calendars because in some years, an extra month is needed to extend the 15598 * days in a year to an entire solar year. The month is represented as a 1-based number 15599 * where 1=first month, 2=second month, etc. 15600 * 15601 * @param {number} year a year for which the number of months is sought 15602 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15603 * cycle is not given, then the year should be given as elapsed years since the beginning 15604 * of the epoch 15605 * @return {number} The number of months in the given year 15606 */ 15607 HanCal.prototype.getNumMonths = function(year, cycle) { 15608 return this.isLeapYear(year, cycle) ? 13 : 12; 15609 }; 15610 15611 /** 15612 * Return the number of days in a particular month in a particular year. This function 15613 * can return a different number for a month depending on the year because of things 15614 * like leap years. 15615 * 15616 * @param {number} month the elapsed month for which the length is sought 15617 * @param {number} year the elapsed year within which that month can be found 15618 * @return {number} the number of days within the given month in the given year 15619 */ 15620 HanCal.prototype.getMonLength = function(month, year) { 15621 // distance between two new moons in Nanjing China 15622 var calc = HanCal._leapYearCalc(year); 15623 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + month * 29); 15624 var postNewMoon = HanCal._newMoonOnOrAfter(priorNewMoon + 1); 15625 return postNewMoon - priorNewMoon; 15626 }; 15627 15628 /** 15629 * Return the equivalent year in the 2820 year cycle that begins on 15630 * Far 1, 474. This particular cycle obeys the cycle-of-years formula 15631 * whereas the others do not specifically. This cycle can be used as 15632 * a proxy for other years outside of the cycle by shifting them into 15633 * the cycle. 15634 * @param {number} year year to find the equivalent cycle year for 15635 * @returns {number} the equivalent cycle year 15636 */ 15637 HanCal.prototype.equivalentCycleYear = function(year) { 15638 var y = year - (year >= 0 ? 474 : 473); 15639 return MathUtils.mod(y, 2820) + 474; 15640 }; 15641 15642 /** 15643 * Return true if the given year is a leap year in the Han calendar. 15644 * If the year is given as a year/cycle combination, then the year should be in the 15645 * range [1,60] and the given cycle is the cycle in which the year is located. If 15646 * the year is greater than 60, then 15647 * it represents the total number of years elapsed in the proleptic calendar since 15648 * the beginning of the Chinese epoch in on 15 Feb, -2636 (Gregorian). In this 15649 * case, the cycle parameter is ignored. 15650 * 15651 * @param {number} year the year for which the leap year information is being sought 15652 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15653 * cycle is not given, then the year should be given as elapsed years since the beginning 15654 * of the epoch 15655 * @return {boolean} true if the given year is a leap year 15656 */ 15657 HanCal.prototype.isLeapYear = function(year, cycle) { 15658 var calc = HanCal._leapYearCalc(year, cycle); 15659 return Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 15660 }; 15661 15662 /** 15663 * Return the month of the year that is the leap month. If the given year is 15664 * not a leap year, then this method will return -1. 15665 * 15666 * @param {number} year the year for which the leap year information is being sought 15667 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15668 * cycle is not given, then the year should be given as elapsed years since the beginning 15669 * of the epoch 15670 * @return {number} the number of the month that is doubled in this leap year, or -1 15671 * if this is not a leap year 15672 */ 15673 HanCal.prototype.getLeapMonth = function(year, cycle) { 15674 var calc = HanCal._leapYearCalc(year, cycle); 15675 15676 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) != 12) { 15677 return -1; // no leap month 15678 } 15679 15680 // search between rd1 and rd2 for the first month with no major solar term. That is our leap month. 15681 var month = 0; 15682 var m = HanCal._newMoonOnOrAfter(calc.m1+1); 15683 while (!HanCal._noMajorST(m)) { 15684 month++; 15685 m = HanCal._newMoonOnOrAfter(m+1); 15686 } 15687 15688 // return the number of the month that is doubled 15689 return month; 15690 }; 15691 15692 /** 15693 * Return the date of Chinese New Years in the given calendar year. 15694 * 15695 * @param {number} year the Chinese year for which the new year information is being sought 15696 * @param {number=} cycle if the given year < 60, this can specify the cycle. If the 15697 * cycle is not given, then the year should be given as elapsed years since the beginning 15698 * of the epoch 15699 * @return {number} the julian day of the beginning of the given year 15700 */ 15701 HanCal.prototype.newYears = function(year, cycle) { 15702 var calc = HanCal._leapYearCalc(year, cycle); 15703 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15704 if (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12 && 15705 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 15706 return HanCal._newMoonOnOrAfter(m2+1); 15707 } 15708 return m2; 15709 }; 15710 15711 /** 15712 * Return the type of this calendar. 15713 * 15714 * @return {string} the name of the type of this calendar 15715 */ 15716 HanCal.prototype.getType = function() { 15717 return this.type; 15718 }; 15719 15720 15721 /* register this calendar for the factory method */ 15722 Calendar._constructors["han"] = HanCal; 15723 15724 15725 15726 /*< HanRataDie.js */ 15727 /* 15728 * HanDate.js - Represent a date in the Han algorithmic calendar 15729 * 15730 * Copyright © 2014-2015, JEDLSoft 15731 * 15732 * Licensed under the Apache License, Version 2.0 (the "License"); 15733 * you may not use this file except in compliance with the License. 15734 * You may obtain a copy of the License at 15735 * 15736 * http://www.apache.org/licenses/LICENSE-2.0 15737 * 15738 * Unless required by applicable law or agreed to in writing, software 15739 * distributed under the License is distributed on an "AS IS" BASIS, 15740 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15741 * 15742 * See the License for the specific language governing permissions and 15743 * limitations under the License. 15744 */ 15745 15746 /* !depends 15747 ilib.js 15748 HanCal.js 15749 MathUtils.js 15750 RataDie.js 15751 */ 15752 15753 15754 /** 15755 * Construct a new Han RD date number object. The constructor parameters can 15756 * contain any of the following properties: 15757 * 15758 * <ul> 15759 * <li><i>unixtime<i> - sets the time of this instance according to the given 15760 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15761 * 15762 * <li><i>julianday</i> - sets the time of this instance according to the given 15763 * Julian Day instance or the Julian Day given as a float 15764 * 15765 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15766 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15767 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15768 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15769 * to 60 and treated as if it were a year in the regular 60-year cycle. 15770 * 15771 * <li><i>year</i> - any integer, including 0 15772 * 15773 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15774 * 15775 * <li><i>day</i> - 1 to 31 15776 * 15777 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15778 * is always done with an unambiguous 24 hour representation 15779 * 15780 * <li><i>minute</i> - 0 to 59 15781 * 15782 * <li><i>second</i> - 0 to 59 15783 * 15784 * <li><i>millisecond</i> - 0 to 999 15785 * 15786 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15787 * </ul> 15788 * 15789 * If the constructor is called with another Han date instance instead of 15790 * a parameter block, the other instance acts as a parameter block and its 15791 * settings are copied into the current instance.<p> 15792 * 15793 * If the constructor is called with no arguments at all or if none of the 15794 * properties listed above are present, then the RD is calculate based on 15795 * the current date at the time of instantiation. <p> 15796 * 15797 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 15798 * specified in the params, it is assumed that they have the smallest possible 15799 * value in the range for the property (zero or one).<p> 15800 * 15801 * 15802 * @private 15803 * @class 15804 * @constructor 15805 * @extends RataDie 15806 * @param {Object=} params parameters that govern the settings and behaviour of this Han RD date 15807 */ 15808 var HanRataDie = function(params) { 15809 this.rd = NaN; 15810 if (params && params.cal) { 15811 this.cal = params.cal; 15812 RataDie.call(this, params); 15813 if (params && typeof(params.callback) === 'function') { 15814 params.callback(this); 15815 } 15816 } else { 15817 new HanCal({ 15818 sync: params && params.sync, 15819 loadParams: params && params.loadParams, 15820 onLoad: ilib.bind(this, function(c) { 15821 this.cal = c; 15822 RataDie.call(this, params); 15823 if (params && typeof(params.callback) === 'function') { 15824 params.callback(this); 15825 } 15826 }) 15827 }); 15828 } 15829 }; 15830 15831 HanRataDie.prototype = new RataDie(); 15832 HanRataDie.prototype.parent = RataDie; 15833 HanRataDie.prototype.constructor = HanRataDie; 15834 15835 /** 15836 * The difference between a zero Julian day and the first Han date 15837 * which is February 15, -2636 (Gregorian). 15838 * @private 15839 * @type number 15840 */ 15841 HanRataDie.epoch = 758325.5; 15842 15843 /** 15844 * Calculate the Rata Die (fixed day) number of the given date from the 15845 * date components. 15846 * 15847 * @protected 15848 * @param {Object} date the date components to calculate the RD from 15849 */ 15850 HanRataDie.prototype._setDateComponents = function(date) { 15851 var calc = HanCal._leapYearCalc(date.year, date.cycle); 15852 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 15853 var newYears; 15854 this.leapYear = (Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12); 15855 if (this.leapYear && (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2)) ) { 15856 newYears = HanCal._newMoonOnOrAfter(m2+1); 15857 } else { 15858 newYears = m2; 15859 } 15860 15861 var priorNewMoon = HanCal._newMoonOnOrAfter(calc.m1 + date.month * 29); // this is a julian day 15862 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(priorNewMoon)); 15863 this.leapMonth = (this.leapYear && HanCal._noMajorST(priorNewMoon) && !this.priorLeapMonth); 15864 15865 var rdtime = (date.hour * 3600000 + 15866 date.minute * 60000 + 15867 date.second * 1000 + 15868 date.millisecond) / 15869 86400000; 15870 15871 /* 15872 console.log("getRataDie: converting " + JSON.stringify(date) + " to an RD"); 15873 console.log("getRataDie: year is " + date.year + " plus cycle " + date.cycle); 15874 console.log("getRataDie: isLeapYear is " + this.leapYear); 15875 console.log("getRataDie: priorNewMoon is " + priorNewMoon); 15876 console.log("getRataDie: day in month is " + date.day); 15877 console.log("getRataDie: rdtime is " + rdtime); 15878 console.log("getRataDie: rd is " + (priorNewMoon + date.day - 1 + rdtime)); 15879 */ 15880 15881 this.rd = priorNewMoon + date.day - 1 + rdtime - RataDie.gregorianEpoch; 15882 }; 15883 15884 /** 15885 * Return the rd number of the particular day of the week on or before the 15886 * given rd. eg. The Sunday on or before the given rd. 15887 * @private 15888 * @param {number} rd the rata die date of the reference date 15889 * @param {number} dayOfWeek the day of the week that is being sought relative 15890 * to the current date 15891 * @return {number} the rd of the day of the week 15892 */ 15893 HanRataDie.prototype._onOrBefore = function(rd, dayOfWeek) { 15894 return rd - MathUtils.mod(Math.floor(rd) - dayOfWeek, 7); 15895 }; 15896 15897 /** 15898 * @protected 15899 * @static 15900 * @param {number} jd1 first julian day 15901 * @param {number} jd2 second julian day 15902 * @returns {boolean} true if there is a leap month earlier in the same year 15903 * as the given months 15904 */ 15905 HanRataDie._priorLeapMonth = function(jd1, jd2) { 15906 return jd2 >= jd1 && 15907 (HanRataDie._priorLeapMonth(jd1, HanCal._newMoonBefore(jd2)) || 15908 HanCal._noMajorST(jd2)); 15909 }; 15910 15911 15912 15913 /*< HanDate.js */ 15914 /* 15915 * HanDate.js - Represent a date in the Han algorithmic calendar 15916 * 15917 * Copyright © 2014-2015, JEDLSoft 15918 * 15919 * Licensed under the Apache License, Version 2.0 (the "License"); 15920 * you may not use this file except in compliance with the License. 15921 * You may obtain a copy of the License at 15922 * 15923 * http://www.apache.org/licenses/LICENSE-2.0 15924 * 15925 * Unless required by applicable law or agreed to in writing, software 15926 * distributed under the License is distributed on an "AS IS" BASIS, 15927 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15928 * 15929 * See the License for the specific language governing permissions and 15930 * limitations under the License. 15931 */ 15932 15933 /* !depends 15934 ilib.js 15935 IDate.js 15936 GregorianDate.js 15937 HanCal.js 15938 Astro.js 15939 JSUtils.js 15940 MathUtils.js 15941 LocaleInfo.js 15942 Locale.js 15943 TimeZone.js 15944 HanRataDie.js 15945 RataDie.js 15946 */ 15947 15948 15949 15950 15951 /** 15952 * @class 15953 * 15954 * Construct a new Han date object. The constructor parameters can 15955 * contain any of the following properties: 15956 * 15957 * <ul> 15958 * <li><i>unixtime<i> - sets the time of this instance according to the given 15959 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970, Gregorian 15960 * 15961 * <li><i>julianday</i> - sets the time of this instance according to the given 15962 * Julian Day instance or the Julian Day given as a float 15963 * 15964 * <li><i>cycle</i> - any integer giving the number of 60-year cycle in which the date is located. 15965 * If the cycle is not given but the year is, it is assumed that the year parameter is a fictitious 15966 * linear count of years since the beginning of the epoch, much like other calendars. This linear 15967 * count is never used. If both the cycle and year are given, the year is wrapped to the range 0 15968 * to 60 and treated as if it were a year in the regular 60-year cycle. 15969 * 15970 * <li><i>year</i> - any integer, including 0 15971 * 15972 * <li><i>month</i> - 1 to 12, where 1 means Farvardin, 2 means Ordibehesht, etc. 15973 * 15974 * <li><i>day</i> - 1 to 31 15975 * 15976 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 15977 * is always done with an unambiguous 24 hour representation 15978 * 15979 * <li><i>minute</i> - 0 to 59 15980 * 15981 * <li><i>second</i> - 0 to 59 15982 * 15983 * <li><i>millisecond</i> - 0 to 999 15984 * 15985 * <li><i>timezone</i> - the TimeZone instance or time zone name as a string 15986 * of this han date. The date/time is kept in the local time. The time zone 15987 * is used later if this date is formatted according to a different time zone and 15988 * the difference has to be calculated, or when the date format has a time zone 15989 * component in it. 15990 * 15991 * <li><i>locale</i> - locale for this han date. If the time zone is not 15992 * given, it can be inferred from this locale. For locales that span multiple 15993 * time zones, the one with the largest population is chosen as the one that 15994 * represents the locale. 15995 * 15996 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 15997 * </ul> 15998 * 15999 * If the constructor is called with another Han date instance instead of 16000 * a parameter block, the other instance acts as a parameter block and its 16001 * settings are copied into the current instance.<p> 16002 * 16003 * If the constructor is called with no arguments at all or if none of the 16004 * properties listed above 16005 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 16006 * components are 16007 * filled in with the current date at the time of instantiation. Note that if 16008 * you do not give the time zone when defaulting to the current time and the 16009 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 16010 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 16011 * Mean Time").<p> 16012 * 16013 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16014 * specified in the params, it is assumed that they have the smallest possible 16015 * value in the range for the property (zero or one).<p> 16016 * 16017 * 16018 * @constructor 16019 * @extends Date 16020 * @param {Object=} params parameters that govern the settings and behaviour of this Han date 16021 */ 16022 var HanDate = function(params) { 16023 params = params || {}; 16024 if (params.locale) { 16025 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 16026 } 16027 if (params.timezone) { 16028 this.timezone = params.timezone; 16029 } 16030 16031 if (!this.timezone) { 16032 if (this.locale) { 16033 new LocaleInfo(this.locale, { 16034 sync: params.sync, 16035 loadParams: params.loadParams, 16036 onLoad: ilib.bind(this, function(li) { 16037 this.li = li; 16038 this.timezone = li.getTimeZone(); 16039 this._init(params); 16040 }) 16041 }); 16042 } else { 16043 this.timezone = "local"; 16044 this._init(params); 16045 } 16046 } else { 16047 this._init(params); 16048 } 16049 }; 16050 16051 HanDate.prototype = new IDate({noinstance: true}); 16052 HanDate.prototype.parent = IDate; 16053 HanDate.prototype.constructor = HanDate; 16054 16055 /** 16056 * Initialize the han date 16057 * @private 16058 */ 16059 HanDate.prototype._init = function (params) { 16060 new HanCal({ 16061 sync: params && typeof(params.sync) === 'boolean' ? params.sync : true, 16062 loadParams: params && params.loadParams, 16063 onLoad: ilib.bind(this, function (cal) { 16064 this.cal = cal; 16065 16066 if (params.year || params.month || params.day || params.hour || 16067 params.minute || params.second || params.millisecond || params.cycle || params.cycleYear) { 16068 if (typeof(params.cycle) !== 'undefined') { 16069 /** 16070 * Cycle number in the Han calendar. 16071 * @type number 16072 */ 16073 this.cycle = parseInt(params.cycle, 10) || 0; 16074 16075 var year = (typeof(params.year) !== 'undefined' ? parseInt(params.year, 10) : parseInt(params.cycleYear, 10)) || 0; 16076 16077 /** 16078 * Year in the Han calendar. 16079 * @type number 16080 */ 16081 this.year = HanCal._getElapsedYear(year, this.cycle); 16082 } else { 16083 if (typeof(params.year) !== 'undefined') { 16084 this.year = parseInt(params.year, 10) || 0; 16085 this.cycle = Math.floor((this.year - 1) / 60); 16086 } else { 16087 this.year = this.cycle = 0; 16088 } 16089 } 16090 16091 /** 16092 * The month number, ranging from 1 to 13 16093 * @type number 16094 */ 16095 this.month = parseInt(params.month, 10) || 1; 16096 16097 /** 16098 * The day of the month. This ranges from 1 to 30. 16099 * @type number 16100 */ 16101 this.day = parseInt(params.day, 10) || 1; 16102 16103 /** 16104 * The hour of the day. This can be a number from 0 to 23, as times are 16105 * stored unambiguously in the 24-hour clock. 16106 * @type number 16107 */ 16108 this.hour = parseInt(params.hour, 10) || 0; 16109 16110 /** 16111 * The minute of the hours. Ranges from 0 to 59. 16112 * @type number 16113 */ 16114 this.minute = parseInt(params.minute, 10) || 0; 16115 16116 /** 16117 * The second of the minute. Ranges from 0 to 59. 16118 * @type number 16119 */ 16120 this.second = parseInt(params.second, 10) || 0; 16121 16122 /** 16123 * The millisecond of the second. Ranges from 0 to 999. 16124 * @type number 16125 */ 16126 this.millisecond = parseInt(params.millisecond, 10) || 0; 16127 16128 // derived properties 16129 16130 /** 16131 * Year in the cycle of the Han calendar 16132 * @type number 16133 */ 16134 this.cycleYear = MathUtils.amod(this.year, 60); 16135 16136 /** 16137 * The day of the year. Ranges from 1 to 384. 16138 * @type number 16139 */ 16140 this.dayOfYear = parseInt(params.dayOfYear, 10); 16141 16142 if (typeof(params.dst) === 'boolean') { 16143 this.dst = params.dst; 16144 } 16145 16146 this.newRd({ 16147 cal: this.cal, 16148 cycle: this.cycle, 16149 year: this.year, 16150 month: this.month, 16151 day: this.day, 16152 hour: this.hour, 16153 minute: this.minute, 16154 second: this.second, 16155 millisecond: this.millisecond, 16156 sync: params.sync, 16157 loadParams: params.loadParams, 16158 callback: ilib.bind(this, function (rd) { 16159 if (rd) { 16160 this.rd = rd; 16161 16162 // add the time zone offset to the rd to convert to UTC 16163 new TimeZone({ 16164 id: this.timezone, 16165 sync: params.sync, 16166 loadParams: params.loadParams, 16167 onLoad: ilib.bind(this, function(tz) { 16168 this.tz = tz; 16169 // getOffsetMillis requires that this.year, this.rd, and this.dst 16170 // are set in order to figure out which time zone rules apply and 16171 // what the offset is at that point in the year 16172 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 16173 if (this.offset !== 0) { 16174 // this newRd can be called synchronously because we already called 16175 // it asynchronously above, so all of the astro data should 16176 // already be loaded. 16177 this.rd = this.newRd({ 16178 cal: this.cal, 16179 rd: this.rd.getRataDie() - this.offset 16180 }); 16181 this._calcLeap(); 16182 } else { 16183 // re-use the derived properties from the RD calculations 16184 this.leapMonth = this.rd.leapMonth; 16185 this.priorLeapMonth = this.rd.priorLeapMonth; 16186 this.leapYear = this.rd.leapYear; 16187 } 16188 16189 this._init2(params); 16190 }) 16191 }); 16192 } else { 16193 this._init2(params); 16194 } 16195 }) 16196 }); 16197 } else { 16198 this._init2(params); 16199 } 16200 }) 16201 }); 16202 }; 16203 16204 /** 16205 * Finish the initialization for the han date. 16206 * @private 16207 */ 16208 HanDate.prototype._init2 = function (params) { 16209 if (!this.rd) { 16210 // init2() may be called without newRd having been called before, 16211 // so we cannot guarantee that the astro data is already loaded. 16212 // That means, we have to treat this as a possibly asynchronous 16213 // call. 16214 this.newRd(JSUtils.merge(params || {}, { 16215 cal: this.cal, 16216 sync: params.sync, 16217 loadParams: params.loadParams, 16218 callback: ilib.bind(this, function(rd) { 16219 this.rd = rd; 16220 this._calcDateComponents(); 16221 16222 if (params && typeof(params.onLoad) === 'function') { 16223 params.onLoad(this); 16224 } 16225 }) 16226 })); 16227 } else { 16228 if (params && typeof(params.onLoad) === 'function') { 16229 params.onLoad(this); 16230 } 16231 } 16232 }; 16233 16234 /** 16235 * Return a new RD for this date type using the given params. 16236 * @protected 16237 * @param {Object=} params the parameters used to create this rata die instance 16238 * @returns {RataDie} the new RD instance for the given params 16239 */ 16240 HanDate.prototype.newRd = function (params) { 16241 return new HanRataDie(params); 16242 }; 16243 16244 /** 16245 * Return the year for the given RD 16246 * @protected 16247 * @param {number} rd RD to calculate from 16248 * @returns {number} the year for the RD 16249 */ 16250 HanDate.prototype._calcYear = function(rd) { 16251 var gregdate = new GregorianDate({ 16252 rd: rd, 16253 timezone: this.timezone 16254 }); 16255 var hanyear = gregdate.year + 2697; 16256 var newYears = this.cal.newYears(hanyear); 16257 return hanyear - ((rd + RataDie.gregorianEpoch < newYears) ? 1 : 0); 16258 }; 16259 16260 /** 16261 * @private 16262 * Calculate the leap year and months from the RD. 16263 */ 16264 HanDate.prototype._calcLeap = function() { 16265 var jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 16266 16267 var calc = HanCal._leapYearCalc(this.year); 16268 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 16269 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 16270 16271 var newYears = (this.leapYear && 16272 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 16273 HanCal._newMoonOnOrAfter(m2+1) : m2; 16274 16275 var m = HanCal._newMoonBefore(jd + 1); 16276 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 16277 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 16278 }; 16279 16280 /** 16281 * @private 16282 * Calculate date components for the given RD date. 16283 */ 16284 HanDate.prototype._calcDateComponents = function () { 16285 var remainder, 16286 jd = this.rd.getRataDie() + RataDie.gregorianEpoch; 16287 16288 // console.log("HanDate._calcDateComponents: calculating for jd " + jd); 16289 16290 if (typeof(this.offset) === "undefined") { 16291 // now offset the jd by the time zone, then recalculate in case we were 16292 // near the year boundary 16293 if (!this.tz) { 16294 this.tz = new TimeZone({id: this.timezone}); 16295 } 16296 this.offset = this.tz.getOffsetMillis(this) / 86400000; 16297 } 16298 16299 if (this.offset !== 0) { 16300 jd += this.offset; 16301 } 16302 16303 // use the Gregorian calendar objects as a convenient way to short-cut some 16304 // of the date calculations 16305 16306 var gregyear = GregorianDate._calcYear(this.rd.getRataDie()); 16307 this.year = gregyear + 2697; 16308 var calc = HanCal._leapYearCalc(this.year); 16309 var m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 16310 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 16311 var newYears = (this.leapYear && 16312 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 16313 HanCal._newMoonOnOrAfter(m2+1) : m2; 16314 16315 // See if it's between Jan 1 and the Chinese new years of that Gregorian year. If 16316 // so, then the Han year is actually the previous one 16317 if (jd < newYears) { 16318 this.year--; 16319 calc = HanCal._leapYearCalc(this.year); 16320 m2 = HanCal._newMoonOnOrAfter(calc.m1+1); 16321 this.leapYear = Math.round((calc.m2 - calc.m1) / 29.530588853000001) === 12; 16322 newYears = (this.leapYear && 16323 (HanCal._noMajorST(calc.m1) || HanCal._noMajorST(m2))) ? 16324 HanCal._newMoonOnOrAfter(m2+1) : m2; 16325 } 16326 // month is elapsed month, not the month number + leap month boolean 16327 var m = HanCal._newMoonBefore(jd + 1); 16328 this.month = Math.round((m - calc.m1) / 29.530588853000001); 16329 16330 this.priorLeapMonth = HanRataDie._priorLeapMonth(newYears, HanCal._newMoonBefore(m)); 16331 this.leapMonth = (this.leapYear && HanCal._noMajorST(m) && !this.priorLeapMonth); 16332 16333 this.cycle = Math.floor((this.year - 1) / 60); 16334 this.cycleYear = MathUtils.amod(this.year, 60); 16335 this.day = Astro._floorToJD(jd) - m + 1; 16336 16337 /* 16338 console.log("HanDate._calcDateComponents: year is " + this.year); 16339 console.log("HanDate._calcDateComponents: isLeapYear is " + this.leapYear); 16340 console.log("HanDate._calcDateComponents: cycle is " + this.cycle); 16341 console.log("HanDate._calcDateComponents: cycleYear is " + this.cycleYear); 16342 console.log("HanDate._calcDateComponents: month is " + this.month); 16343 console.log("HanDate._calcDateComponents: isLeapMonth is " + this.leapMonth); 16344 console.log("HanDate._calcDateComponents: day is " + this.day); 16345 */ 16346 16347 // floor to the start of the julian day 16348 remainder = jd - Astro._floorToJD(jd); 16349 16350 // console.log("HanDate._calcDateComponents: time remainder is " + remainder); 16351 16352 // now convert to milliseconds for the rest of the calculation 16353 remainder = Math.round(remainder * 86400000); 16354 16355 this.hour = Math.floor(remainder/3600000); 16356 remainder -= this.hour * 3600000; 16357 16358 this.minute = Math.floor(remainder/60000); 16359 remainder -= this.minute * 60000; 16360 16361 this.second = Math.floor(remainder/1000); 16362 remainder -= this.second * 1000; 16363 16364 this.millisecond = remainder; 16365 }; 16366 16367 /** 16368 * Return the year within the Chinese cycle of this date. Cycles are 60 16369 * years long, and the value returned from this method is the number of the year 16370 * within this cycle. The year returned from getYear() is the total elapsed 16371 * years since the beginning of the Chinese epoch and does not include 16372 * the cycles. 16373 * 16374 * @return {number} the year within the current Chinese cycle 16375 */ 16376 HanDate.prototype.getCycleYears = function() { 16377 return this.cycleYear; 16378 }; 16379 16380 /** 16381 * Return the Chinese cycle number of this date. Cycles are 60 years long, 16382 * and the value returned from getCycleYear() is the number of the year 16383 * within this cycle. The year returned from getYear() is the total elapsed 16384 * years since the beginning of the Chinese epoch and does not include 16385 * the cycles. 16386 * 16387 * @return {number} the current Chinese cycle 16388 */ 16389 HanDate.prototype.getCycles = function() { 16390 return this.cycle; 16391 }; 16392 16393 /** 16394 * Return whether the year of this date is a leap year in the Chinese Han 16395 * calendar. 16396 * 16397 * @return {boolean} true if the year of this date is a leap year in the 16398 * Chinese Han calendar. 16399 */ 16400 HanDate.prototype.isLeapYear = function() { 16401 return this.leapYear; 16402 }; 16403 16404 /** 16405 * Return whether the month of this date is a leap month in the Chinese Han 16406 * calendar. 16407 * 16408 * @return {boolean} true if the month of this date is a leap month in the 16409 * Chinese Han calendar. 16410 */ 16411 HanDate.prototype.isLeapMonth = function() { 16412 return this.leapMonth; 16413 }; 16414 16415 /** 16416 * Return the day of the week of this date. The day of the week is encoded 16417 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 16418 * 16419 * @return {number} the day of the week 16420 */ 16421 HanDate.prototype.getDayOfWeek = function() { 16422 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 16423 return MathUtils.mod(rd, 7); 16424 }; 16425 16426 /** 16427 * Return the ordinal day of the year. Days are counted from 1 and proceed linearly up to 16428 * 365, regardless of months or weeks, etc. That is, Farvardin 1st is day 1, and 16429 * December 31st is 365 in regular years, or 366 in leap years. 16430 * @return {number} the ordinal day of the year 16431 */ 16432 HanDate.prototype.getDayOfYear = function() { 16433 var newYears = this.cal.newYears(this.year); 16434 var priorNewMoon = HanCal._newMoonOnOrAfter(newYears + (this.month -1) * 29); 16435 return priorNewMoon - newYears + this.day; 16436 }; 16437 16438 /** 16439 * Return the era for this date as a number. The value for the era for Han 16440 * calendars is -1 for "before the han era" (BP) and 1 for "the han era" (anno 16441 * persico or AP). 16442 * BP dates are any date before Farvardin 1, 1 AP. In the proleptic Han calendar, 16443 * there is a year 0, so any years that are negative or zero are BP. 16444 * @return {number} 1 if this date is in the common era, -1 if it is before the 16445 * common era 16446 */ 16447 HanDate.prototype.getEra = function() { 16448 return (this.year < 1) ? -1 : 1; 16449 }; 16450 16451 /** 16452 * Return the name of the calendar that governs this date. 16453 * 16454 * @return {string} a string giving the name of the calendar 16455 */ 16456 HanDate.prototype.getCalendar = function() { 16457 return "han"; 16458 }; 16459 16460 // register with the factory method 16461 IDate._constructors["han"] = HanDate; 16462 16463 16464 /*< EthiopicCal.js */ 16465 /* 16466 * ethiopic.js - Represent a Ethiopic calendar object. 16467 * 16468 * Copyright © 2015,2018, JEDLSoft 16469 * 16470 * Licensed under the Apache License, Version 2.0 (the "License"); 16471 * you may not use this file except in compliance with the License. 16472 * You may obtain a copy of the License at 16473 * 16474 * http://www.apache.org/licenses/LICENSE-2.0 16475 * 16476 * Unless required by applicable law or agreed to in writing, software 16477 * distributed under the License is distributed on an "AS IS" BASIS, 16478 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16479 * 16480 * See the License for the specific language governing permissions and 16481 * limitations under the License. 16482 */ 16483 16484 /* !depends Calendar.js MathUtils.js */ 16485 16486 16487 16488 /** 16489 * @class 16490 * Construct a new Ethiopic calendar object. This class encodes information about 16491 * a Ethiopic calendar.<p> 16492 * 16493 * @param {Object=} options Options governing the construction of this instance 16494 * @constructor 16495 * @extends Calendar 16496 */ 16497 var EthiopicCal = function(options) { 16498 this.type = "ethiopic"; 16499 16500 if (options && typeof(options.onLoad) === "function") { 16501 options.onLoad(this); 16502 } 16503 }; 16504 16505 /** 16506 * Return the number of months in the given year. The number of months in a year varies 16507 * for lunar calendars because in some years, an extra month is needed to extend the 16508 * days in a year to an entire solar year. The month is represented as a 1-based number 16509 * where 1=Maskaram, 2=Teqemt, etc. until 13=Paguemen. 16510 * 16511 * @param {number} year a year for which the number of months is sought 16512 */ 16513 EthiopicCal.prototype.getNumMonths = function(year) { 16514 return 13; 16515 }; 16516 16517 /** 16518 * Return the number of days in a particular month in a particular year. This function 16519 * can return a different number for a month depending on the year because of things 16520 * like leap years. 16521 * 16522 * @param {number|string} month the month for which the length is sought 16523 * @param {number} year the year within which that month can be found 16524 * @return {number} the number of days within the given month in the given year 16525 */ 16526 EthiopicCal.prototype.getMonLength = function(month, year) { 16527 var m = month; 16528 switch (typeof(m)) { 16529 case "string": 16530 m = parseInt(m, 10); 16531 break; 16532 case "function": 16533 case "object": 16534 case "undefined": 16535 return 30; 16536 } 16537 if (m < 13) { 16538 return 30; 16539 } else { 16540 return this.isLeapYear(year) ? 6 : 5; 16541 } 16542 }; 16543 16544 /** 16545 * Return true if the given year is a leap year in the Ethiopic calendar. 16546 * The year parameter may be given as a number, or as a JulDate object. 16547 * @param {number|EthiopicDate|string} year the year for which the leap year information is being sought 16548 * @return {boolean} true if the given year is a leap year 16549 */ 16550 EthiopicCal.prototype.isLeapYear = function(year) { 16551 var y = year; 16552 switch (typeof(y)) { 16553 case "string": 16554 y = parseInt(y, 10); 16555 break; 16556 case "object": 16557 if (typeof(y.year) !== "number") { // in case it is an IDate object 16558 return false; 16559 } 16560 y = y.year; 16561 break; 16562 case "function": 16563 case "undefined": 16564 return false; 16565 break; 16566 } 16567 return MathUtils.mod(y, 4) === 3; 16568 }; 16569 16570 /** 16571 * Return the type of this calendar. 16572 * 16573 * @return {string} the name of the type of this calendar 16574 */ 16575 EthiopicCal.prototype.getType = function() { 16576 return this.type; 16577 }; 16578 16579 16580 /* register this calendar for the factory method */ 16581 Calendar._constructors["ethiopic"] = EthiopicCal; 16582 16583 16584 16585 /*< EthiopicRataDie.js */ 16586 /* 16587 * EthiopicRataDie.js - Represent an RD date in the Ethiopic calendar 16588 * 16589 * Copyright © 2015, JEDLSoft 16590 * 16591 * Licensed under the Apache License, Version 2.0 (the "License"); 16592 * you may not use this file except in compliance with the License. 16593 * You may obtain a copy of the License at 16594 * 16595 * http://www.apache.org/licenses/LICENSE-2.0 16596 * 16597 * Unless required by applicable law or agreed to in writing, software 16598 * distributed under the License is distributed on an "AS IS" BASIS, 16599 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16600 * 16601 * See the License for the specific language governing permissions and 16602 * limitations under the License. 16603 */ 16604 16605 /* !depends 16606 EthiopicCal.js 16607 RataDie.js 16608 */ 16609 16610 16611 /** 16612 * @class 16613 * Construct a new Ethiopic RD date number object. The constructor parameters can 16614 * contain any of the following properties: 16615 * 16616 * <ul> 16617 * <li><i>unixtime<i> - sets the time of this instance according to the given 16618 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 16619 * 16620 * <li><i>julianday</i> - sets the time of this instance according to the given 16621 * Julian Day instance or the Julian Day given as a float 16622 * 16623 * <li><i>year</i> - any integer, including 0 16624 * 16625 * <li><i>month</i> - 1 to 12, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 16626 * 16627 * <li><i>day</i> - 1 to 30 16628 * 16629 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16630 * is always done with an unambiguous 24 hour representation 16631 * 16632 * <li><i>minute</i> - 0 to 59 16633 * 16634 * <li><i>second</i> - 0 to 59 16635 * 16636 * <li><i>millisecond</i> - 0 to 999 16637 * 16638 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16639 * </ul> 16640 * 16641 * If the constructor is called with another Ethiopic date instance instead of 16642 * a parameter block, the other instance acts as a parameter block and its 16643 * settings are copied into the current instance.<p> 16644 * 16645 * If the constructor is called with no arguments at all or if none of the 16646 * properties listed above are present, then the RD is calculate based on 16647 * the current date at the time of instantiation. <p> 16648 * 16649 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 16650 * specified in the params, it is assumed that they have the smallest possible 16651 * value in the range for the property (zero or one).<p> 16652 * 16653 * 16654 * @private 16655 * @constructor 16656 * @extends RataDie 16657 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic RD date 16658 */ 16659 var EthiopicRataDie = function(params) { 16660 this.cal = params && params.cal || new EthiopicCal(); 16661 this.rd = NaN; 16662 RataDie.call(this, params); 16663 }; 16664 16665 EthiopicRataDie.prototype = new RataDie(); 16666 EthiopicRataDie.prototype.parent = RataDie; 16667 EthiopicRataDie.prototype.constructor = EthiopicRataDie; 16668 16669 /** 16670 * The difference between the zero Julian day and the first Ethiopic date 16671 * of Friday, August 29, 8 CE Julian at 6:00am UTC.<p> 16672 * 16673 * See <a href="http://us.wow.com/wiki/Time_in_Ethiopia?s_chn=90&s_pt=aolsem&v_t=aolsem" 16674 * Time in Ethiopia</a> for information about how time is handled in Ethiopia. 16675 * 16676 * @protected 16677 * @type number 16678 */ 16679 EthiopicRataDie.prototype.epoch = 1724219.75; 16680 16681 /** 16682 * Calculate the Rata Die (fixed day) number of the given date from the 16683 * date components. 16684 * 16685 * @protected 16686 * @param {Object} date the date components to calculate the RD from 16687 */ 16688 EthiopicRataDie.prototype._setDateComponents = function(date) { 16689 var year = date.year; 16690 var years = 365 * (year - 1) + Math.floor(year/4); 16691 var dayInYear = (date.month-1) * 30 + date.day; 16692 var rdtime = (date.hour * 3600000 + 16693 date.minute * 60000 + 16694 date.second * 1000 + 16695 date.millisecond) / 16696 86400000; 16697 16698 /* 16699 console.log("calcRataDie: converting " + JSON.stringify(parts)); 16700 console.log("getRataDie: year is " + years); 16701 console.log("getRataDie: day in year is " + dayInYear); 16702 console.log("getRataDie: rdtime is " + rdtime); 16703 console.log("getRataDie: rd is " + (years + dayInYear + rdtime)); 16704 */ 16705 16706 this.rd = years + dayInYear + rdtime; 16707 }; 16708 16709 16710 16711 /*< EthiopicDate.js */ 16712 /* 16713 * EthiopicDate.js - Represent a date in the Ethiopic calendar 16714 * 16715 * Copyright © 2015, 2018, JEDLSoft 16716 * 16717 * Licensed under the Apache License, Version 2.0 (the "License"); 16718 * you may not use this file except in compliance with the License. 16719 * You may obtain a copy of the License at 16720 * 16721 * http://www.apache.org/licenses/LICENSE-2.0 16722 * 16723 * Unless required by applicable law or agreed to in writing, software 16724 * distributed under the License is distributed on an "AS IS" BASIS, 16725 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 16726 * 16727 * See the License for the specific language governing permissions and 16728 * limitations under the License. 16729 */ 16730 16731 /* !depends 16732 ilib.js 16733 IDate.js 16734 EthiopicCal.js 16735 MathUtils.js 16736 Locale.js 16737 LocaleInfo.js 16738 TimeZone.js 16739 EthiopicRataDie.js 16740 */ 16741 16742 16743 16744 /** 16745 * @class 16746 * Construct a new date object for the Ethiopic Calendar. The constructor can be called 16747 * with a parameter object that contains any of the following properties: 16748 * 16749 * <ul> 16750 * <li><i>unixtime<i> - sets the time of this instance according to the given 16751 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 16752 * <li><i>julianday</i> - the Julian Day to set into this date 16753 * <li><i>year</i> - any integer 16754 * <li><i>month</i> - 1 to 13, where 1 means Maskaram, 2 means Teqemt, etc., and 13 means Paguemen 16755 * <li><i>day</i> - 1 to 30 16756 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 16757 * is always done with an unambiguous 24 hour representation 16758 * <li><i>minute</i> - 0 to 59 16759 * <li><i>second</i> - 0 to 59 16760 * <li><i>millisecond<i> - 0 to 999 16761 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 16762 * of this ethiopic date. The date/time is kept in the local time. The time zone 16763 * is used later if this date is formatted according to a different time zone and 16764 * the difference has to be calculated, or when the date format has a time zone 16765 * component in it. 16766 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 16767 * given, it can be inferred from this locale. For locales that span multiple 16768 * time zones, the one with the largest population is chosen as the one that 16769 * represents the locale. 16770 * 16771 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 16772 * </ul> 16773 * 16774 * If called with another Ethiopic date argument, the date components of the given 16775 * date are copied into the current one.<p> 16776 * 16777 * If the constructor is called with no arguments at all or if none of the 16778 * properties listed above 16779 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 16780 * components are 16781 * filled in with the current date at the time of instantiation. Note that if 16782 * you do not give the time zone when defaulting to the current time and the 16783 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 16784 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 16785 * Mean Time").<p> 16786 * 16787 * 16788 * @constructor 16789 * @extends IDate 16790 * @param {Object=} params parameters that govern the settings and behaviour of this Ethiopic date 16791 */ 16792 var EthiopicDate = function(params) { 16793 this.cal = new EthiopicCal(); 16794 16795 params = params || {}; 16796 16797 if (typeof(params.noinstance) === 'boolean' && params.noinstance) { 16798 // for doing inheritance, so don't need to fill in the data. The inheriting class only wants the methods. 16799 return; 16800 } 16801 if (params.timezone) { 16802 this.timezone = params.timezone; 16803 } 16804 if (params.locale) { 16805 this.locale = (typeof(params.locale) === 'string') ? new Locale(params.locale) : params.locale; 16806 } 16807 16808 if (!this.timezone) { 16809 if (this.locale) { 16810 new LocaleInfo(this.locale, { 16811 sync: params.sync, 16812 loadParams: params.loadParams, 16813 onLoad: ilib.bind(this, function(li) { 16814 this.li = li; 16815 this.timezone = li.getTimeZone(); 16816 this._init(params); 16817 }) 16818 }); 16819 } else { 16820 this.timezone = "local"; 16821 this._init(params); 16822 } 16823 } else { 16824 this._init(params); 16825 } 16826 }; 16827 16828 EthiopicDate.prototype = new IDate({ noinstance: true }); 16829 EthiopicDate.prototype.parent = IDate; 16830 EthiopicDate.prototype.constructor = EthiopicDate; 16831 16832 /** 16833 * Initialize this instance 16834 * @private 16835 */ 16836 EthiopicDate.prototype._init = function (params) { 16837 new TimeZone({ 16838 id: this.timezone, 16839 sync: params.sync, 16840 loadParams: params.loadParams, 16841 onLoad: ilib.bind(this, function(tz) { 16842 this.tz = tz; 16843 16844 if (params.year || params.month || params.day || params.hour || 16845 params.minute || params.second || params.millisecond ) { 16846 /** 16847 * Year in the Ethiopic calendar. 16848 * @type number 16849 */ 16850 this.year = parseInt(params.year, 10) || 0; 16851 /** 16852 * The month number, ranging from 1 (Maskaram) to 13 (Paguemen). 16853 * @type number 16854 */ 16855 this.month = parseInt(params.month, 10) || 1; 16856 /** 16857 * The day of the month. This ranges from 1 to 30. 16858 * @type number 16859 */ 16860 this.day = parseInt(params.day, 10) || 1; 16861 /** 16862 * The hour of the day. This can be a number from 0 to 23, as times are 16863 * stored unambiguously in the 24-hour clock. 16864 * @type number 16865 */ 16866 this.hour = parseInt(params.hour, 10) || 0; 16867 /** 16868 * The minute of the hours. Ranges from 0 to 59. 16869 * @type number 16870 */ 16871 this.minute = parseInt(params.minute, 10) || 0; 16872 /** 16873 * The second of the minute. Ranges from 0 to 59. 16874 * @type number 16875 */ 16876 this.second = parseInt(params.second, 10) || 0; 16877 /** 16878 * The millisecond of the second. Ranges from 0 to 999. 16879 * @type number 16880 */ 16881 this.millisecond = parseInt(params.millisecond, 10) || 0; 16882 16883 /** 16884 * The day of the year. Ranges from 1 to 366. 16885 * @type number 16886 */ 16887 this.dayOfYear = parseInt(params.dayOfYear, 10); 16888 16889 if (typeof(params.dst) === 'boolean') { 16890 this.dst = params.dst; 16891 } 16892 16893 this.rd = this.newRd(this); 16894 16895 // add the time zone offset to the rd to convert to UTC 16896 // getOffsetMillis requires that this.year, this.rd, and this.dst 16897 // are set in order to figure out which time zone rules apply and 16898 // what the offset is at that point in the year 16899 this.offset = this.tz._getOffsetMillisWallTime(this) / 86400000; 16900 if (this.offset !== 0) { 16901 this.rd = this.newRd({ 16902 rd: this.rd.getRataDie() - this.offset 16903 }); 16904 } 16905 } 16906 16907 if (!this.rd) { 16908 this.rd = this.newRd(params); 16909 this._calcDateComponents(); 16910 } 16911 16912 if (typeof(params.onLoad) === "function") { 16913 params.onLoad(this); 16914 } 16915 }) 16916 }); 16917 }; 16918 16919 /** 16920 * Return a new RD for this date type using the given params. 16921 * @protected 16922 * @param {Object=} params the parameters used to create this rata die instance 16923 * @returns {RataDie} the new RD instance for the given params 16924 */ 16925 EthiopicDate.prototype.newRd = function (params) { 16926 return new EthiopicRataDie(params); 16927 }; 16928 16929 /** 16930 * Return the year for the given RD 16931 * @protected 16932 * @param {number} rd RD to calculate from 16933 * @returns {number} the year for the RD 16934 */ 16935 EthiopicDate.prototype._calcYear = function(rd) { 16936 var year = Math.floor((4*(Math.floor(rd)-1) + 1463)/1461); 16937 16938 return year; 16939 }; 16940 16941 /** 16942 * Calculate date components for the given RD date. 16943 * @protected 16944 */ 16945 EthiopicDate.prototype._calcDateComponents = function () { 16946 var remainder, 16947 rd = this.rd.getRataDie(); 16948 16949 this.year = this._calcYear(rd); 16950 16951 if (typeof(this.offset) === "undefined") { 16952 this.year = this._calcYear(rd); 16953 16954 // now offset the RD by the time zone, then recalculate in case we were 16955 // near the year boundary 16956 if (!this.tz) { 16957 this.tz = new TimeZone({id: this.timezone}); 16958 } 16959 this.offset = this.tz.getOffsetMillis(this) / 86400000; 16960 } 16961 16962 if (this.offset !== 0) { 16963 rd += this.offset; 16964 this.year = this._calcYear(rd); 16965 } 16966 16967 var jan1 = this.newRd({ 16968 year: this.year, 16969 month: 1, 16970 day: 1, 16971 hour: 0, 16972 minute: 0, 16973 second: 0, 16974 millisecond: 0 16975 }); 16976 remainder = rd + 1 - jan1.getRataDie(); 16977 16978 this.month = Math.floor((remainder-1)/30) + 1; 16979 remainder = remainder - (this.month-1) * 30; 16980 16981 this.day = Math.floor(remainder); 16982 remainder -= this.day; 16983 // now convert to milliseconds for the rest of the calculation 16984 remainder = Math.round(remainder * 86400000); 16985 16986 this.hour = Math.floor(remainder/3600000); 16987 remainder -= this.hour * 3600000; 16988 16989 this.minute = Math.floor(remainder/60000); 16990 remainder -= this.minute * 60000; 16991 16992 this.second = Math.floor(remainder/1000); 16993 remainder -= this.second * 1000; 16994 16995 this.millisecond = remainder; 16996 }; 16997 16998 /** 16999 * Return the day of the week of this date. The day of the week is encoded 17000 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 17001 * 17002 * @return {number} the day of the week 17003 */ 17004 EthiopicDate.prototype.getDayOfWeek = function() { 17005 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 17006 return MathUtils.mod(rd-4, 7); 17007 }; 17008 17009 /** 17010 * Return the name of the calendar that governs this date. 17011 * 17012 * @return {string} a string giving the name of the calendar 17013 */ 17014 EthiopicDate.prototype.getCalendar = function() { 17015 return "ethiopic"; 17016 }; 17017 17018 //register with the factory method 17019 IDate._constructors["ethiopic"] = EthiopicDate; 17020 17021 17022 17023 /*< CopticCal.js */ 17024 /* 17025 * CopticCal.js - Represent a Coptic calendar object. 17026 * 17027 * Copyright © 2015,2018, JEDLSoft 17028 * 17029 * Licensed under the Apache License, Version 2.0 (the "License"); 17030 * you may not use this file except in compliance with the License. 17031 * You may obtain a copy of the License at 17032 * 17033 * http://www.apache.org/licenses/LICENSE-2.0 17034 * 17035 * Unless required by applicable law or agreed to in writing, software 17036 * distributed under the License is distributed on an "AS IS" BASIS, 17037 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17038 * 17039 * See the License for the specific language governing permissions and 17040 * limitations under the License. 17041 */ 17042 17043 17044 /* !depends Calendar.js Utils.js EthiopicCal.js */ 17045 17046 17047 /** 17048 * @class 17049 * Construct a new Coptic calendar object. This class encodes information about 17050 * a Coptic calendar.<p> 17051 * 17052 * @param {Object=} options Options governing the construction of this instance 17053 * @constructor 17054 * @extends EthiopicCal 17055 */ 17056 var CopticCal = function(options) { 17057 this.type = "coptic"; 17058 17059 if (options && typeof(options.onLoad) === "function") { 17060 options.onLoad(this); 17061 } 17062 }; 17063 17064 CopticCal.prototype = new EthiopicCal(); 17065 CopticCal.prototype.parent = EthiopicCal; 17066 CopticCal.prototype.constructor = CopticCal; 17067 17068 17069 /* register this calendar for the factory method */ 17070 Calendar._constructors["coptic"] = CopticCal; 17071 17072 17073 17074 /*< CopticRataDie.js */ 17075 /* 17076 * CopticRataDie.js - Represent an RD date in the Coptic calendar 17077 * 17078 * Copyright © 2015, JEDLSoft 17079 * 17080 * Licensed under the Apache License, Version 2.0 (the "License"); 17081 * you may not use this file except in compliance with the License. 17082 * You may obtain a copy of the License at 17083 * 17084 * http://www.apache.org/licenses/LICENSE-2.0 17085 * 17086 * Unless required by applicable law or agreed to in writing, software 17087 * distributed under the License is distributed on an "AS IS" BASIS, 17088 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17089 * 17090 * See the License for the specific language governing permissions and 17091 * limitations under the License. 17092 */ 17093 17094 /* !depends 17095 CopticCal.js 17096 JSUtils.js 17097 EthiopicRataDie.js 17098 */ 17099 17100 17101 /** 17102 * @class 17103 * Construct a new Coptic RD date number object. The constructor parameters can 17104 * contain any of the following properties: 17105 * 17106 * <ul> 17107 * <li><i>unixtime<i> - sets the time of this instance according to the given 17108 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970. 17109 * 17110 * <li><i>julianday</i> - sets the time of this instance according to the given 17111 * Julian Day instance or the Julian Day given as a float 17112 * 17113 * <li><i>year</i> - any integer, including 0 17114 * 17115 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 17116 * 17117 * <li><i>day</i> - 1 to 30 17118 * 17119 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 17120 * is always done with an unambiguous 24 hour representation 17121 * 17122 * <li><i>minute</i> - 0 to 59 17123 * 17124 * <li><i>second</i> - 0 to 59 17125 * 17126 * <li><i>millisecond</i> - 0 to 999 17127 * 17128 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 17129 * </ul> 17130 * 17131 * If the constructor is called with another Coptic date instance instead of 17132 * a parameter block, the other instance acts as a parameter block and its 17133 * settings are copied into the current instance.<p> 17134 * 17135 * If the constructor is called with no arguments at all or if none of the 17136 * properties listed above are present, then the RD is calculate based on 17137 * the current date at the time of instantiation. <p> 17138 * 17139 * If any of the properties from <i>year</i> through <i>millisecond</i> are not 17140 * specified in the params, it is assumed that they have the smallest possible 17141 * value in the range for the property (zero or one).<p> 17142 * 17143 * 17144 * @private 17145 * @constructor 17146 * @extends EthiopicRataDie 17147 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic RD date 17148 */ 17149 var CopticRataDie = function(params) { 17150 this.cal = params && params.cal || new CopticCal(); 17151 this.rd = NaN; 17152 /** 17153 * The difference between the zero Julian day and the first Coptic date 17154 * of Friday, August 29, 284 CE Julian at 7:00am UTC. 17155 * @private 17156 * @type number 17157 */ 17158 this.epoch = 1825028.5; 17159 17160 var tmp = {}; 17161 if (params) { 17162 JSUtils.shallowCopy(params, tmp); 17163 } 17164 tmp.cal = this.cal; // override the cal parameter that may be passed in 17165 EthiopicRataDie.call(this, tmp); 17166 }; 17167 17168 CopticRataDie.prototype = new EthiopicRataDie(); 17169 CopticRataDie.prototype.parent = EthiopicRataDie; 17170 CopticRataDie.prototype.constructor = CopticRataDie; 17171 17172 17173 /*< CopticDate.js */ 17174 /* 17175 * CopticDate.js - Represent a date in the Coptic calendar 17176 * 17177 * Copyright © 2015, JEDLSoft 17178 * 17179 * Licensed under the Apache License, Version 2.0 (the "License"); 17180 * you may not use this file except in compliance with the License. 17181 * You may obtain a copy of the License at 17182 * 17183 * http://www.apache.org/licenses/LICENSE-2.0 17184 * 17185 * Unless required by applicable law or agreed to in writing, software 17186 * distributed under the License is distributed on an "AS IS" BASIS, 17187 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17188 * 17189 * See the License for the specific language governing permissions and 17190 * limitations under the License. 17191 */ 17192 17193 /* !depends 17194 ilib.js 17195 IDate.js 17196 CopticCal.js 17197 MathUtils.js 17198 Locale.js 17199 EthiopicDate.js 17200 CopticRataDie.js 17201 */ 17202 17203 17204 17205 17206 /** 17207 * @class 17208 * Construct a new date object for the Coptic Calendar. The constructor can be called 17209 * with a parameter object that contains any of the following properties: 17210 * 17211 * <ul> 17212 * <li><i>unixtime<i> - sets the time of this instance according to the given 17213 * unix time. Unix time is the number of milliseconds since midnight on Jan 1, 1970 (Gregorian). 17214 * <li><i>julianday</i> - the Julian Day to set into this date 17215 * <li><i>year</i> - any integer 17216 * <li><i>month</i> - 1 to 13, where 1 means Thoout, 2 means Paope, etc., and 13 means Epagomene 17217 * <li><i>day</i> - 1 to 30 17218 * <li><i>hour</i> - 0 to 23. A formatter is used to display 12 hour clocks, but this representation 17219 * is always done with an unambiguous 24 hour representation 17220 * <li><i>minute</i> - 0 to 59 17221 * <li><i>second</i> - 0 to 59 17222 * <li><i>millisecond<i> - 0 to 999 17223 * <li><i>locale</i> - the TimeZone instance or time zone name as a string 17224 * of this coptic date. The date/time is kept in the local time. The time zone 17225 * is used later if this date is formatted according to a different time zone and 17226 * the difference has to be calculated, or when the date format has a time zone 17227 * component in it. 17228 * <li><i>timezone</i> - the time zone of this instance. If the time zone is not 17229 * given, it can be inferred from this locale. For locales that span multiple 17230 * time zones, the one with the largest population is chosen as the one that 17231 * represents the locale. 17232 * 17233 * <li><i>date</i> - use the given intrinsic Javascript date to initialize this one. 17234 * </ul> 17235 * 17236 * If called with another Coptic date argument, the date components of the given 17237 * date are copied into the current one.<p> 17238 * 17239 * If the constructor is called with no arguments at all or if none of the 17240 * properties listed above 17241 * from <i>unixtime</i> through <i>millisecond</i> are present, then the date 17242 * components are 17243 * filled in with the current date at the time of instantiation. Note that if 17244 * you do not give the time zone when defaulting to the current time and the 17245 * time zone for all of ilib was not set with <i>ilib.setTimeZone()</i>, then the 17246 * time zone will default to UTC ("Universal Time, Coordinated" or "Greenwich 17247 * Mean Time").<p> 17248 * 17249 * 17250 * @constructor 17251 * @extends EthiopicDate 17252 * @param {Object=} params parameters that govern the settings and behaviour of this Coptic date 17253 */ 17254 var CopticDate = function(params) { 17255 this.rd = NaN; // clear these out so that the EthiopicDate constructor can set it 17256 var newparams = ilib.extend({}, params); 17257 newparams.onLoad = function(ed) { 17258 ed.cal = new CopticCal(); 17259 if (typeof(params.onLoad) === "function") { 17260 params.onLoad(ed); 17261 } 17262 }; 17263 EthiopicDate.call(this, params); 17264 }; 17265 17266 CopticDate.prototype = new EthiopicDate({noinstance: true}); 17267 CopticDate.prototype.parent = EthiopicDate.prototype; 17268 CopticDate.prototype.constructor = CopticDate; 17269 17270 /** 17271 * Return a new RD for this date type using the given params. 17272 * @protected 17273 * @param {Object=} params the parameters used to create this rata die instance 17274 * @returns {RataDie} the new RD instance for the given params 17275 */ 17276 CopticDate.prototype.newRd = function (params) { 17277 return new CopticRataDie(params); 17278 }; 17279 17280 /** 17281 * Return the day of the week of this date. The day of the week is encoded 17282 * as number from 0 to 6, with 0=Sunday, 1=Monday, etc., until 6=Saturday. 17283 * 17284 * @return {number} the day of the week 17285 */ 17286 CopticDate.prototype.getDayOfWeek = function() { 17287 var rd = Math.floor(this.rd.getRataDie() + (this.offset || 0)); 17288 return MathUtils.mod(rd-3, 7); 17289 }; 17290 17291 /** 17292 * Return the name of the calendar that governs this date. 17293 * 17294 * @return {string} a string giving the name of the calendar 17295 */ 17296 CopticDate.prototype.getCalendar = function() { 17297 return "coptic"; 17298 }; 17299 17300 //register with the factory method 17301 IDate._constructors["coptic"] = CopticDate; 17302 17303 17304 /*< CType.js */ 17305 /* 17306 * CType.js - Character type definitions 17307 * 17308 * Copyright © 2012-2015, JEDLSoft 17309 * 17310 * Licensed under the Apache License, Version 2.0 (the "License"); 17311 * you may not use this file except in compliance with the License. 17312 * You may obtain a copy of the License at 17313 * 17314 * http://www.apache.org/licenses/LICENSE-2.0 17315 * 17316 * Unless required by applicable law or agreed to in writing, software 17317 * distributed under the License is distributed on an "AS IS" BASIS, 17318 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17319 * 17320 * See the License for the specific language governing permissions and 17321 * limitations under the License. 17322 */ 17323 17324 // !depends ilib.js SearchUtils.js Utils.js IString.js 17325 17326 // !data ctype 17327 17328 17329 /** 17330 * Provides a set of static routines that return information about characters. 17331 * These routines emulate the C-library ctype functions. The characters must be 17332 * encoded in utf-16, as no other charsets are currently supported. Only the first 17333 * character of the given string is tested. 17334 * @namespace 17335 */ 17336 var CType = {}; 17337 17338 17339 /** 17340 * Actual implementation for withinRange. Searches the given object for ranges. 17341 * The range names are taken from the Unicode range names in 17342 * http://www.unicode.org/Public/UNIDATA/extracted/DerivedGeneralCategory.txt 17343 * 17344 * <ul> 17345 * <li>Cn - Unassigned 17346 * <li>Lu - Uppercase_Letter 17347 * <li>Ll - Lowercase_Letter 17348 * <li>Lt - Titlecase_Letter 17349 * <li>Lm - Modifier_Letter 17350 * <li>Lo - Other_Letter 17351 * <li>Mn - Nonspacing_Mark 17352 * <li>Me - Enclosing_Mark 17353 * <li>Mc - Spacing_Mark 17354 * <li>Nd - Decimal_Number 17355 * <li>Nl - Letter_Number 17356 * <li>No - Other_Number 17357 * <li>Zs - Space_Separator 17358 * <li>Zl - Line_Separator 17359 * <li>Zp - Paragraph_Separator 17360 * <li>Cc - Control 17361 * <li>Cf - Format 17362 * <li>Co - Private_Use 17363 * <li>Cs - Surrogate 17364 * <li>Pd - Dash_Punctuation 17365 * <li>Ps - Open_Punctuation 17366 * <li>Pe - Close_Punctuation 17367 * <li>Pc - Connector_Punctuation 17368 * <li>Po - Other_Punctuation 17369 * <li>Sm - Math_Symbol 17370 * <li>Sc - Currency_Symbol 17371 * <li>Sk - Modifier_Symbol 17372 * <li>So - Other_Symbol 17373 * <li>Pi - Initial_Punctuation 17374 * <li>Pf - Final_Punctuation 17375 * </ul> 17376 * 17377 * @protected 17378 * @param {number} num code point of the character to examine 17379 * @param {string} rangeName the name of the range to check 17380 * @param {Object} obj object containing the character range data 17381 * @return {boolean} true if the first character is within the named 17382 * range 17383 */ 17384 CType._inRange = function(num, rangeName, obj) { 17385 var range; 17386 if (num < 0 || !rangeName || !obj) { 17387 return false; 17388 } 17389 17390 range = obj[rangeName]; 17391 if (!range) { 17392 return false; 17393 } 17394 17395 var compare = function(singlerange, target) { 17396 if (singlerange.length === 1) { 17397 return singlerange[0] - target; 17398 } else { 17399 return target < singlerange[0] ? singlerange[0] - target : 17400 (target > singlerange[1] ? singlerange[1] - target : 0); 17401 } 17402 }; 17403 var result = SearchUtils.bsearch(num, range, compare); 17404 return result < range.length && compare(range[result], num) === 0; 17405 }; 17406 17407 /** 17408 * Return whether or not the first character is within the named range 17409 * of Unicode characters. The valid list of range names are taken from 17410 * the Unicode 6.0 spec. Characters in all ranges of Unicode are supported, 17411 * including those supported in Javascript via UTF-16. Currently, this method 17412 * supports the following range names: 17413 * 17414 * <ul> 17415 * <li><i>ascii</i> - basic ASCII 17416 * <li><i>latin</i> - Latin, Latin Extended Additional, Latin-1 supplement, Latin Extended-C, Latin Extended-D, Latin Extended-E 17417 * <li><i>armenian</i> 17418 * <li><i>greek</i> - Greek, Greek Extended 17419 * <li><i>cyrillic</i> - Cyrillic, Cyrillic Extended-A, Cyrillic Extended-B, Cyrillic Extended-C, Cyrillic Supplement 17420 * <li><i>georgian</i> - Georgian, Georgian Supplement 17421 * <li><i>glagolitic</i> - Glagolitic, Glagolitic Supplement 17422 * <li><i>gothic</i> 17423 * <li><i>ogham</i> 17424 * <li><i>oldpersian</i> 17425 * <li><i>runic</i> 17426 * <li><i>ipa</i> - IPA, Phonetic Extensions, Phonetic Extensions Supplement 17427 * <li><i>phonetic</i> 17428 * <li><i>modifiertone</i> - Modifier Tone Letters 17429 * <li><i>spacing</i> 17430 * <li><i>diacritics</i> 17431 * <li><i>halfmarks</i> - Combining Half Marks 17432 * <li><i>small</i> - Small Form Variants 17433 * <li><i>bamum</i> - Bamum, Bamum Supplement 17434 * <li><i>ethiopic</i> - Ethiopic, Ethiopic Extended, Ethiopic Extended-A 17435 * <li><i>nko</i> 17436 * <li><i>osmanya</i> 17437 * <li><i>tifinagh</i> 17438 * <li><i>val</i> 17439 * <li><i>arabic</i> - Arabic, Arabic Supplement, Arabic Presentation Forms-A, 17440 * Arabic Presentation Forms-B, Arabic Mathematical Alphabetic Symbols 17441 * <li><i>carlan</i> 17442 * <li><i>hebrew</i> 17443 * <li><i>mandaic</i> 17444 * <li><i>samaritan</i> 17445 * <li><i>syriac</i> 17446 * <li><i>mongolian</i> 17447 * <li><i>phagspa</i> 17448 * <li><i>tibetan</i> 17449 * <li><i>bengali</i> 17450 * <li><i>devanagari</i> - Devanagari, Devanagari Extended 17451 * <li><i>gujarati</i> 17452 * <li><i>gurmukhi</i> 17453 * <li><i>kannada</i> 17454 * <li><i>lepcha</i> 17455 * <li><i>limbu</i> 17456 * <li><i>malayalam</i> 17457 * <li><i>meetaimayek</i> 17458 * <li><i>olchiki</i> 17459 * <li><i>oriya</i> 17460 * <li><i>saurashtra</i> 17461 * <li><i>sinhala</i> 17462 * <li><i>sylotinagri</i> - Syloti Nagri 17463 * <li><i>tangut</i> 17464 * <li><i>tamil</i> 17465 * <li><i>telugu</i> 17466 * <li><i>thaana</i> 17467 * <li><i>vedic</i> 17468 * <li><i>batak</i> 17469 * <li><i>balinese</i> 17470 * <li><i>buginese</i> 17471 * <li><i>cham</i> 17472 * <li><i>javanese</i> 17473 * <li><i>kayahli</i> 17474 * <li><i>khmer</i> 17475 * <li><i>lao</i> 17476 * <li><i>myanmar</i> - Myanmar, Myanmar Extended-A, Myanmar Extended-B 17477 * <li><i>newtailue</i> 17478 * <li><i>rejang</i> 17479 * <li><i>sundanese</i> - Sundanese, Sundanese Supplement 17480 * <li><i>taile</i> 17481 * <li><i>taitham</i> 17482 * <li><i>taiviet</i> 17483 * <li><i>thai</i> 17484 * <li><i>buhld</i> 17485 * <li><i>hanunoo</i> 17486 * <li><i>tagalog</i> 17487 * <li><i>tagbanwa</i> 17488 * <li><i>bopomofo</i> - Bopomofo, Bopomofo Extended 17489 * <li><i>cjk</i> - the CJK unified ideographs (Han), CJK Unified Ideographs 17490 * Extension A, CJK Unified Ideographs Extension B, CJK Unified Ideographs 17491 * Extension C, CJK Unified Ideographs Extension D, Ideographic Description 17492 * Characters (=isIdeo()) 17493 * <li><i>cjkcompatibility</i> - CJK Compatibility, CJK Compatibility 17494 * Ideographs, CJK Compatibility Forms, CJK Compatibility Ideographs Supplement 17495 * <li><i>cjkradicals</i> - the CJK radicals, KangXi radicals 17496 * <li><i>hangul</i> - Hangul Jamo, Hangul Syllables, Hangul Jamo Extended-A, 17497 * Hangul Jamo Extended-B, Hangul Compatibility Jamo 17498 * <li><i>cjkpunct</i> - CJK symbols and punctuation 17499 * <li><i>cjkstrokes</i> - CJK strokes 17500 * <li><i>hiragana</i> 17501 * <li><i>katakana</i> - Katakana, Katakana Phonetic Extensions, Kana Supplement 17502 * <li><i>kanbun</i> 17503 * <li><i>lisu</i> 17504 * <li><i>yi</i> - Yi Syllables, Yi Radicals 17505 * <li><i>cherokee</i> 17506 * <li><i>canadian</i> - Unified Canadian Aboriginal Syllabics, Unified Canadian 17507 * Aboriginal Syllabics Extended 17508 * <li><i>presentation</i> - Alphabetic presentation forms 17509 * <li><i>vertical</i> - Vertical Forms 17510 * <li><i>width</i> - Halfwidth and Fullwidth Forms 17511 * <li><i>punctuation</i> - General punctuation, Supplemental Punctuation 17512 * <li><i>box</i> - Box Drawing 17513 * <li><i>block</i> - Block Elements 17514 * <li><i>letterlike</i> - Letterlike symbols 17515 * <li><i>mathematical</i> - Mathematical alphanumeric symbols, Miscellaneous 17516 * Mathematical Symbols-A, Miscellaneous Mathematical Symbols-B 17517 * <li><i>enclosedalpha</i> - Enclosed alphanumerics, Enclosed Alphanumeric Supplement 17518 * <li><i>enclosedcjk</i> - Enclosed CJK letters and months, Enclosed Ideographic Supplement 17519 * <li><i>cjkcompatibility</i> - CJK compatibility 17520 * <li><i>apl</i> - APL symbols 17521 * <li><i>controlpictures</i> - Control pictures 17522 * <li><i>misc</i> - Miscellaneous technical 17523 * <li><i>ocr</i> - Optical character recognition (OCR) 17524 * <li><i>combining</i> - Combining Diacritical Marks, Combining Diacritical Marks 17525 * for Symbols, Combining Diacritical Marks Supplement, Combining Diacritical Marks Extended 17526 * <li><i>digits</i> - ASCII digits (=isDigit()) 17527 * <li><i>indicnumber</i> - Common Indic Number Forms 17528 * <li><i>numbers</i> - Number forms 17529 * <li><i>supersub</i> - Superscripts and Subscripts 17530 * <li><i>arrows</i> - Arrows, Miscellaneous Symbols and Arrows, Supplemental Arrows-A, 17531 * Supplemental Arrows-B, Supplemental Arrows-C 17532 * <li><i>operators</i> - Mathematical operators, supplemental 17533 * mathematical operators 17534 * <li><i>geometric</i> - Geometric shapes, Geometric shapes extended 17535 * <li><i>ancient</i> - Ancient symbols 17536 * <li><i>braille</i> - Braille patterns 17537 * <li><i>currency</i> - Currency symbols 17538 * <li><i>dingbats</i> 17539 * <li><i>gamesymbols</i> 17540 * <li><i>yijing</i> - Yijing Hexagram Symbols 17541 * <li><i>specials</i> 17542 * <li><i>variations</i> - Variation Selectors, Variation Selectors Supplement 17543 * <li><i>privateuse</i> - Private Use Area, Supplementary Private Use Area-A, 17544 * Supplementary Private Use Area-B 17545 * <li><i>supplementarya</i> - Supplementary private use area-A 17546 * <li><i>supplementaryb</i> - Supplementary private use area-B 17547 * <li><i>highsurrogates</i> - High Surrogates, High Private Use Surrogates 17548 * <li><i>lowsurrogates</i> 17549 * <li><i>reserved</i> 17550 * <li><i>noncharacters</i> 17551 * <li><i>copticnumber</i> - coptic epact numbers 17552 * <li><i>oldpermic</i> - old permic 17553 * <li><i>albanian</i> - albanian 17554 * <li><i>lineara</i> - linear a 17555 * <li><i>meroitic</i> - meroitic cursive 17556 * <li><i>oldnortharabian</i> - old north arabian 17557 * <li><i>oldhungarian</i> - Supplementary private use area-A 17558 * <li><i>sorasompeng</i> - sora sompeng 17559 * <li><i>warangciti</i> - warang citi 17560 * <li><i>paucinhau</i> - pau cin hau 17561 * <li><i>bassavah</i> - bassa vah 17562 * <li><i>pahawhhmong</i> - pahawh hmong 17563 * <li><i>shorthandformat</i> - shorthand format controls 17564 * <li><i>suttonsignwriting</i> - sutton signwriting 17565 * <li><i>pictographs</i> - miscellaneous symbols and pictographs, supplemental symbols and pictographs 17566 * <li><i>ornamentaldingbats</i> - ornamental dingbats 17567 * </ul><p> 17568 * 17569 * 17570 * @protected 17571 * @param {string|IString|number} ch character or code point to examine 17572 * @param {string} rangeName the name of the range to check 17573 * @return {boolean} true if the first character is within the named 17574 * range 17575 */ 17576 CType.withinRange = function(ch, rangeName) { 17577 if (!rangeName) { 17578 return false; 17579 } 17580 var num; 17581 switch (typeof(ch)) { 17582 case 'number': 17583 num = ch; 17584 break; 17585 case 'string': 17586 num = IString.toCodePoint(ch, 0); 17587 break; 17588 case 'undefined': 17589 return false; 17590 default: 17591 num = ch._toCodePoint(0); 17592 break; 17593 } 17594 17595 return CType._inRange(num, rangeName.toLowerCase(), ilib.data.ctype); 17596 }; 17597 17598 /** 17599 * @protected 17600 * @param {boolean} sync 17601 * @param {Object|undefined} loadParams 17602 * @param {function(*)|undefined} onLoad 17603 */ 17604 CType._init = function(sync, loadParams, onLoad) { 17605 CType._load("ctype", sync, loadParams, onLoad); 17606 }; 17607 17608 /** 17609 * @protected 17610 * @param {string} name 17611 * @param {boolean} sync 17612 * @param {Object|undefined} loadParams 17613 * @param {function(*)|undefined} onLoad 17614 */ 17615 CType._load = function (name, sync, loadParams, onLoad) { 17616 if (!ilib.data[name]) { 17617 var loadName = name ? name + ".json" : "CType.json"; 17618 Utils.loadData({ 17619 object: "CType", 17620 name: loadName, 17621 locale: "-", 17622 nonlocale: true, 17623 sync: sync, 17624 loadParams: loadParams, 17625 callback: ilib.bind(this, function(ct) { 17626 ilib.data[name] = ct; 17627 if (onLoad && typeof(onLoad) === 'function') { 17628 onLoad(ilib.data[name]); 17629 } 17630 }) 17631 }); 17632 } else { 17633 if (onLoad && typeof(onLoad) === 'function') { 17634 onLoad(ilib.data[name]); 17635 } 17636 } 17637 }; 17638 17639 17640 17641 /*< isDigit.js */ 17642 /* 17643 * isDigit.js - Character type is digit 17644 * 17645 * Copyright © 2012-2015, JEDLSoft 17646 * 17647 * Licensed under the Apache License, Version 2.0 (the "License"); 17648 * you may not use this file except in compliance with the License. 17649 * You may obtain a copy of the License at 17650 * 17651 * http://www.apache.org/licenses/LICENSE-2.0 17652 * 17653 * Unless required by applicable law or agreed to in writing, software 17654 * distributed under the License is distributed on an "AS IS" BASIS, 17655 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17656 * 17657 * See the License for the specific language governing permissions and 17658 * limitations under the License. 17659 */ 17660 17661 // !depends CType.js IString.js ilib.js 17662 17663 // !data ctype ctype_n 17664 17665 17666 /** 17667 * Return whether or not the first character is a digit character in the 17668 * Latin script.<p> 17669 * 17670 * @static 17671 * @param {string|IString|number} ch character or code point to examine 17672 * @return {boolean} true if the first character is a digit character in the 17673 * Latin script. 17674 */ 17675 var isDigit = function (ch) { 17676 var num; 17677 switch (typeof(ch)) { 17678 case 'number': 17679 num = ch; 17680 break; 17681 case 'string': 17682 num = IString.toCodePoint(ch, 0); 17683 break; 17684 case 'undefined': 17685 return false; 17686 default: 17687 num = ch._toCodePoint(0); 17688 break; 17689 } 17690 return ilib.data.ctype ? CType._inRange(num, 'Nd', ilib.data.ctype_n) : (num >= 0x30 && num <= 0x39); 17691 }; 17692 17693 /** 17694 * @protected 17695 * @param {boolean} sync 17696 * @param {Object|undefined} loadParams 17697 * @param {function(*)|undefined} onLoad 17698 */ 17699 isDigit._init = function (sync, loadParams, onLoad) { 17700 CType._load("ctype_n", sync, loadParams, onLoad); 17701 }; 17702 17703 17704 17705 /*< isSpace.js */ 17706 /* 17707 * isSpace.js - Character type is space char 17708 * 17709 * Copyright © 2012-2015, JEDLSoft 17710 * 17711 * Licensed under the Apache License, Version 2.0 (the "License"); 17712 * you may not use this file except in compliance with the License. 17713 * You may obtain a copy of the License at 17714 * 17715 * http://www.apache.org/licenses/LICENSE-2.0 17716 * 17717 * Unless required by applicable law or agreed to in writing, software 17718 * distributed under the License is distributed on an "AS IS" BASIS, 17719 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17720 * 17721 * See the License for the specific language governing permissions and 17722 * limitations under the License. 17723 */ 17724 17725 // !depends CType.js IString.js 17726 17727 // !data ctype ctype_z 17728 17729 17730 17731 /** 17732 * Return whether or not the first character is a whitespace character.<p> 17733 * 17734 * @static 17735 * @param {string|IString|number} ch character or code point to examine 17736 * @return {boolean} true if the first character is a whitespace character. 17737 */ 17738 var isSpace = function (ch) { 17739 var num; 17740 switch (typeof(ch)) { 17741 case 'number': 17742 num = ch; 17743 break; 17744 case 'string': 17745 num = IString.toCodePoint(ch, 0); 17746 break; 17747 case 'undefined': 17748 return false; 17749 default: 17750 num = ch._toCodePoint(0); 17751 break; 17752 } 17753 17754 return ilib.data.ctype && ilib.data.ctype_z ? 17755 (CType._inRange(num, 'space', ilib.data.ctype) || 17756 CType._inRange(num, 'Zs', ilib.data.ctype_z) || 17757 CType._inRange(num, 'Zl', ilib.data.ctype_z) || 17758 CType._inRange(num, 'Zp', ilib.data.ctype_z)) : 17759 (ch === ' ' || num === 0xA0 || 17760 (num >= 0x09 && num <= 0x0D)); 17761 }; 17762 17763 /** 17764 * @protected 17765 * @param {boolean} sync 17766 * @param {Object|undefined} loadParams 17767 * @param {function(*)|undefined} onLoad 17768 */ 17769 isSpace._init = function (sync, loadParams, onLoad) { 17770 CType._load("ctype_z", sync, loadParams, function () { 17771 CType._init(sync, loadParams, onLoad); 17772 }); 17773 }; 17774 17775 17776 /*< Currency.js */ 17777 /* 17778 * Currency.js - Currency definition 17779 * 17780 * Copyright © 2012-2015, JEDLSoft 17781 * 17782 * Licensed under the Apache License, Version 2.0 (the "License"); 17783 * you may not use this file except in compliance with the License. 17784 * You may obtain a copy of the License at 17785 * 17786 * http://www.apache.org/licenses/LICENSE-2.0 17787 * 17788 * Unless required by applicable law or agreed to in writing, software 17789 * distributed under the License is distributed on an "AS IS" BASIS, 17790 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 17791 * 17792 * See the License for the specific language governing permissions and 17793 * limitations under the License. 17794 */ 17795 17796 // !depends ilib.js Utils.js Locale.js LocaleInfo.js 17797 17798 // !data currency 17799 17800 17801 /** 17802 * @class 17803 * Create a new currency information instance. Instances of this class encode 17804 * information about a particular currency.<p> 17805 * 17806 * Note: that if you are looking to format currency for display, please see 17807 * the number formatting class {NumFmt}. This class only gives information 17808 * about currencies.<p> 17809 * 17810 * The options can contain any of the following properties: 17811 * 17812 * <ul> 17813 * <li><i>locale</i> - specify the locale for this instance 17814 * <li><i>code</i> - find info on a specific currency with the given ISO 4217 code 17815 * <li><i>sign</i> - search for a currency that uses this sign 17816 * <li><i>onLoad</i> - a callback function to call when the currency data is fully 17817 * loaded. When the onLoad option is given, this class will attempt to 17818 * load any missing locale data using the ilib loader callback. 17819 * When the constructor is done (even if the data is already preassembled), the 17820 * onLoad function is called with the current instance as a parameter, so this 17821 * callback can be used with preassembled or dynamic loading or a mix of the two. 17822 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 17823 * asynchronously. If this option is given as "false", then the "onLoad" 17824 * callback must be given, as the instance returned from this constructor will 17825 * not be usable for a while. 17826 * <li><i>loadParams</i> - an object containing parameters to pass to the 17827 * loader callback function when locale data is missing. The parameters are not 17828 * interpretted or modified in any way. They are simply passed along. The object 17829 * may contain any property/value pairs as long as the calling code is in 17830 * agreement with the loader callback function as to what those parameters mean. 17831 * </ul> 17832 * 17833 * When searching for a currency by its sign, this class cannot guarantee 17834 * that it will return info about a specific currency. The reason is that currency 17835 * signs are sometimes shared between different currencies and the sign is 17836 * therefore ambiguous. If you need a 17837 * guarantee, find the currency using the code instead.<p> 17838 * 17839 * The way this class finds a currency by sign is the following. If the sign is 17840 * unambiguous, then 17841 * the currency is returned. If there are multiple currencies that use the same 17842 * sign, and the current locale uses that sign, then the default currency for 17843 * the current locale is returned. If there are multiple, but the current locale 17844 * does not use that sign, then the currency with the largest circulation is 17845 * returned. For example, if you are in the en-GB locale, and the sign is "$", 17846 * then this class will notice that there are multiple currencies with that 17847 * sign (USD, CAD, AUD, HKD, MXP, etc.) Since "$" is not used in en-GB, it will 17848 * pick the one with the largest circulation, which in this case is the US Dollar 17849 * (USD).<p> 17850 * 17851 * If neither the code or sign property is set, the currency that is most common 17852 * for the locale 17853 * will be used instead. If the locale is not set, the default locale will be used. 17854 * If the code is given, but it is not found in the list of known currencies, this 17855 * constructor will throw an exception. If the sign is given, but it is not found, 17856 * this constructor will default to the currency for the current locale. If both 17857 * the code and sign properties are given, then the sign property will be ignored 17858 * and only the code property used. If the locale is given, but it is not a known 17859 * locale, this class will default to the default locale instead.<p> 17860 * 17861 * 17862 * @constructor 17863 * @param options {Object} a set of properties to govern how this instance is constructed. 17864 * @throws "currency xxx is unknown" when the given currency code is not in the list of 17865 * known currencies. xxx is replaced with the requested code. 17866 */ 17867 var Currency = function (options) { 17868 if (options) { 17869 if (options.code) { 17870 this.code = options.code; 17871 } 17872 if (options.locale) { 17873 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 17874 } 17875 if (options.sign) { 17876 this.sign = options.sign; 17877 } 17878 if (options.loadParams) { 17879 this.loadParams = options.loadParams; 17880 } 17881 } else { 17882 options = {sync: true}; 17883 } 17884 17885 if (typeof(options.sync) === 'undefined') { 17886 options.sync = true; 17887 } 17888 17889 this.locale = this.locale || new Locale(); 17890 if (typeof(ilib.data.currency) === 'undefined') { 17891 Utils.loadData({ 17892 name: "currency.json", 17893 object: "Currency", 17894 locale: "-", 17895 sync: this.sync, 17896 loadParams: this.loadParams, 17897 callback: ilib.bind(this, function(currency) { 17898 ilib.data.currency = currency; 17899 this._loadLocinfo(options); 17900 }) 17901 }); 17902 } else { 17903 this._loadLocinfo(options); 17904 } 17905 }; 17906 17907 /** 17908 * Return an array of the ids for all ISO 4217 currencies that 17909 * this copy of ilib knows about. 17910 * 17911 * @static 17912 * @return {Array.<string>} an array of currency ids that this copy of ilib knows about. 17913 */ 17914 Currency.getAvailableCurrencies = function() { 17915 var ret = [], 17916 cur, 17917 currencies = new ResBundle({ 17918 name: "currency" 17919 }).getResObj(); 17920 17921 for (cur in currencies) { 17922 if (cur && currencies[cur]) { 17923 ret.push(cur); 17924 } 17925 } 17926 17927 return ret; 17928 }; 17929 17930 Currency.prototype = { 17931 /** 17932 * @private 17933 */ 17934 _loadLocinfo: function(options) { 17935 new LocaleInfo(this.locale, { 17936 sync: options.sync, 17937 loadParams: options.loadParams, 17938 onLoad: ilib.bind(this, function (li) { 17939 var currInfo; 17940 17941 this.locinfo = li; 17942 if (this.code) { 17943 currInfo = ilib.data.currency[this.code]; 17944 if (!currInfo) { 17945 if (options.sync) { 17946 throw "currency " + this.code + " is unknown"; 17947 } else if (typeof(options.onLoad) === "function") { 17948 options.onLoad(undefined); 17949 return; 17950 } 17951 } 17952 } else if (this.sign) { 17953 currInfo = ilib.data.currency[this.sign]; // maybe it is really a code... 17954 if (typeof(currInfo) !== 'undefined') { 17955 this.code = this.sign; 17956 } else { 17957 this.code = this.locinfo.getCurrency(); 17958 currInfo = ilib.data.currency[this.code]; 17959 if (currInfo.sign !== this.sign) { 17960 // current locale does not use the sign, so search for it 17961 for (var cur in ilib.data.currency) { 17962 if (cur && ilib.data.currency[cur]) { 17963 currInfo = ilib.data.currency[cur]; 17964 if (currInfo.sign === this.sign) { 17965 // currency data is already ordered so that the currency with the 17966 // largest circulation is at the beginning, so all we have to do 17967 // is take the first one in the list that matches 17968 this.code = cur; 17969 break; 17970 } 17971 } 17972 } 17973 } 17974 } 17975 } 17976 17977 if (!currInfo || !this.code) { 17978 this.code = this.locinfo.getCurrency(); 17979 currInfo = ilib.data.currency[this.code]; 17980 } 17981 17982 this.name = currInfo.name; 17983 this.fractionDigits = currInfo.decimals; 17984 this.sign = currInfo.sign; 17985 17986 if (typeof(options.onLoad) === 'function') { 17987 options.onLoad(this); 17988 } 17989 }) 17990 }); 17991 }, 17992 17993 /** 17994 * Return the ISO 4217 currency code for this instance. 17995 * @return {string} the ISO 4217 currency code for this instance 17996 */ 17997 getCode: function () { 17998 return this.code; 17999 }, 18000 18001 /** 18002 * Return the default number of fraction digits that is typically used 18003 * with this type of currency. 18004 * @return {number} the number of fraction digits for this currency 18005 */ 18006 getFractionDigits: function () { 18007 return this.fractionDigits; 18008 }, 18009 18010 /** 18011 * Return the sign commonly used to represent this currency. 18012 * @return {string} the sign commonly used to represent this currency 18013 */ 18014 getSign: function () { 18015 return this.sign; 18016 }, 18017 18018 /** 18019 * Return the name of the currency in English. 18020 * @return {string} the name of the currency in English 18021 */ 18022 getName: function () { 18023 return this.name; 18024 }, 18025 18026 /** 18027 * Return the locale for this currency. If the options to the constructor 18028 * included a locale property in order to find the currency that is appropriate 18029 * for that locale, then the locale is returned here. If the options did not 18030 * include a locale, then this method returns undefined. 18031 * @return {Locale} the locale used in the constructor of this instance, 18032 * or undefined if no locale was given in the constructor 18033 */ 18034 getLocale: function () { 18035 return this.locale; 18036 } 18037 }; 18038 18039 18040 18041 /*< INumber.js */ 18042 /* 18043 * INumber.js - Parse a number in any locale 18044 * 18045 * Copyright © 2012-2015, JEDLSoft 18046 * 18047 * Licensed under the Apache License, Version 2.0 (the "License"); 18048 * you may not use this file except in compliance with the License. 18049 * You may obtain a copy of the License at 18050 * 18051 * http://www.apache.org/licenses/LICENSE-2.0 18052 * 18053 * Unless required by applicable law or agreed to in writing, software 18054 * distributed under the License is distributed on an "AS IS" BASIS, 18055 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18056 * 18057 * See the License for the specific language governing permissions and 18058 * limitations under the License. 18059 */ 18060 18061 /* 18062 !depends 18063 ilib.js 18064 Locale.js 18065 isDigit.js 18066 isSpace.js 18067 LocaleInfo.js 18068 Currency.js 18069 */ 18070 18071 18072 18073 18074 18075 18076 /** 18077 * @class 18078 * Parse a string as a number, ignoring all locale-specific formatting.<p> 18079 * 18080 * This class is different from the standard Javascript parseInt() and parseFloat() 18081 * functions in that the number to be parsed can have formatting characters in it 18082 * that are not supported by those two 18083 * functions, and it handles numbers written in other locales properly. For example, 18084 * if you pass the string "203,231.23" to the parseFloat() function in Javascript, it 18085 * will return you the number 203. The INumber class will parse it correctly and 18086 * the value() function will return the number 203231.23. If you pass parseFloat() the 18087 * string "203.231,23" with the locale set to de-DE, it will return you 203 again. This 18088 * class will return the correct number 203231.23 again.<p> 18089 * 18090 * The options object may contain any of the following properties: 18091 * 18092 * <ul> 18093 * <li><i>locale</i> - specify the locale of the string to parse. This is used to 18094 * figure out what the decimal point character is. If not specified, the default locale 18095 * for the app or browser is used. 18096 * <li><i>type</i> - specify whether this string should be interpretted as a number, 18097 * currency, or percentage amount. When the number is interpretted as a currency 18098 * amount, the getCurrency() method will return something useful, otherwise it will 18099 * return undefined. If 18100 * the number is to be interpretted as percentage amount and there is a percentage sign 18101 * in the string, then the number will be returned 18102 * as a fraction from the valueOf() method. If there is no percentage sign, then the 18103 * number will be returned as a regular number. That is "58.3%" will be returned as the 18104 * number 0.583 but "58.3" will be returned as 58.3. Valid values for this property 18105 * are "number", "currency", and "percentage". Default if this is not specified is 18106 * "number". 18107 * <li><i>onLoad</i> - a callback function to call when the locale data is fully 18108 * loaded. When the onLoad option is given, this class will attempt to 18109 * load any missing locale data using the ilib loader callback. 18110 * When the constructor is done (even if the data is already preassembled), the 18111 * onLoad function is called with the current instance as a parameter, so this 18112 * callback can be used with preassembled or dynamic loading or a mix of the two. 18113 * 18114 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 18115 * asynchronously. If this option is given as "false", then the "onLoad" 18116 * callback must be given, as the instance returned from this constructor will 18117 * not be usable for a while. 18118 * 18119 * <li><i>loadParams</i> - an object containing parameters to pass to the 18120 * loader callback function when locale data is missing. The parameters are not 18121 * interpretted or modified in any way. They are simply passed along. The object 18122 * may contain any property/value pairs as long as the calling code is in 18123 * agreement with the loader callback function as to what those parameters mean. 18124 * </ul> 18125 * <p> 18126 * 18127 * This class is named INumber ("ilib number") so as not to conflict with the 18128 * built-in Javascript Number class. 18129 * 18130 * @constructor 18131 * @param {string|number|INumber|Number|undefined} str a string to parse as a number, or a number value 18132 * @param {Object=} options Options controlling how the instance should be created 18133 */ 18134 var INumber = function (str, options) { 18135 var i, stripped = "", 18136 sync = true; 18137 18138 this.locale = new Locale(); 18139 this.type = "number"; 18140 18141 if (options) { 18142 if (options.locale) { 18143 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 18144 } 18145 if (options.type) { 18146 switch (options.type) { 18147 case "number": 18148 case "currency": 18149 case "percentage": 18150 this.type = options.type; 18151 break; 18152 default: 18153 break; 18154 } 18155 } 18156 if (typeof(options.sync) !== 'undefined') { 18157 sync = !!options.sync; 18158 } 18159 } else { 18160 options = {sync: true}; 18161 } 18162 18163 isDigit._init(sync, options.loadParams, ilib.bind(this, function() { 18164 isSpace._init(sync, options.loadParams, ilib.bind(this, function() { 18165 new LocaleInfo(this.locale, { 18166 sync: sync, 18167 loadParams: options.loadParams, 18168 onLoad: ilib.bind(this, function (li) { 18169 this.li = li; 18170 this.decimal = li.getDecimalSeparator(); 18171 var nativeDecimal = this.li.getNativeDecimalSeparator() || ""; 18172 18173 switch (typeof(str)) { 18174 case 'string': 18175 // stripping should work for all locales, because you just ignore all the 18176 // formatting except the decimal char 18177 var unary = true; // looking for the unary minus still? 18178 var lastNumericChar = 0; 18179 this.str = str || "0"; 18180 i = 0; 18181 for (i = 0; i < this.str.length; i++) { 18182 if (unary && this.str.charAt(i) === '-') { 18183 unary = false; 18184 stripped += this.str.charAt(i); 18185 lastNumericChar = i; 18186 } else if (isDigit(this.str.charAt(i))) { 18187 stripped += this.str.charAt(i); 18188 unary = false; 18189 lastNumericChar = i; 18190 } else if (this.str.charAt(i) === this.decimal || this.str.charAt(i) === nativeDecimal) { 18191 stripped += "."; // always convert to period 18192 unary = false; 18193 lastNumericChar = i; 18194 } // else ignore 18195 } 18196 // record what we actually parsed 18197 this.parsed = this.str.substring(0, lastNumericChar+1); 18198 18199 /** @type {number} */ 18200 this.value = parseFloat(this._mapToLatinDigits(stripped)); 18201 break; 18202 case 'number': 18203 this.str = "" + str; 18204 this.value = str; 18205 break; 18206 18207 case 'object': 18208 // call parseFloat to coerse the type to number 18209 this.value = parseFloat(str.valueOf()); 18210 this.str = "" + this.value; 18211 break; 18212 18213 case 'undefined': 18214 this.value = 0; 18215 this.str = "0"; 18216 break; 18217 } 18218 18219 switch (this.type) { 18220 default: 18221 // don't need to do anything special for other types 18222 break; 18223 case "percentage": 18224 if (this.str.indexOf(li.getPercentageSymbol()) !== -1) { 18225 this.value /= 100; 18226 } 18227 break; 18228 case "currency": 18229 stripped = ""; 18230 i = 0; 18231 while (i < this.str.length && 18232 !isDigit(this.str.charAt(i)) && 18233 !isSpace(this.str.charAt(i))) { 18234 stripped += this.str.charAt(i++); 18235 } 18236 if (stripped.length === 0) { 18237 while (i < this.str.length && 18238 isDigit(this.str.charAt(i)) || 18239 isSpace(this.str.charAt(i)) || 18240 this.str.charAt(i) === '.' || 18241 this.str.charAt(i) === ',' ) { 18242 i++; 18243 } 18244 while (i < this.str.length && 18245 !isDigit(this.str.charAt(i)) && 18246 !isSpace(this.str.charAt(i))) { 18247 stripped += this.str.charAt(i++); 18248 } 18249 } 18250 new Currency({ 18251 locale: this.locale, 18252 sign: stripped, 18253 sync: sync, 18254 loadParams: options.loadParams, 18255 onLoad: ilib.bind(this, function (cur) { 18256 this.currency = cur; 18257 if (options && typeof(options.onLoad) === 'function') { 18258 options.onLoad(this); 18259 } 18260 }) 18261 }); 18262 return; 18263 } 18264 18265 if (options && typeof(options.onLoad) === 'function') { 18266 options.onLoad(this); 18267 } 18268 }) 18269 }); 18270 })); 18271 })); 18272 }; 18273 18274 INumber.prototype = { 18275 /** 18276 * @private 18277 */ 18278 _mapToLatinDigits: function(str) { 18279 // only map if there are actual native digits 18280 var digits = this.li.getNativeDigits(); 18281 if (!digits) return str; 18282 18283 var digitMap = {}; 18284 for (var i = 0; i < digits.length; i++) { 18285 digitMap[digits[i]] = String(i); 18286 } 18287 var decimal = this.li.getNativeDecimalSeparator(); 18288 18289 return str.split("").map(function(ch) { 18290 if (ch == decimal) return "."; 18291 return digitMap[ch] || ch; 18292 }).join(""); 18293 }, 18294 18295 /** 18296 * Return the locale for this formatter instance. 18297 * @return {Locale} the locale instance for this formatter 18298 */ 18299 getLocale: function () { 18300 return this.locale; 18301 }, 18302 18303 /** 18304 * Return the original string that this number instance was created with. 18305 * @return {string} the original string 18306 */ 18307 toString: function () { 18308 return this.str; 18309 }, 18310 18311 /** 18312 * If the type of this INumber instance is "currency", then the parser will attempt 18313 * to figure out which currency this amount represents. The amount can be written 18314 * with any of the currency signs or ISO 4217 codes that are currently 18315 * recognized by ilib, and the currency signs may occur before or after the 18316 * numeric portion of the string. If no currency can be recognized, then the 18317 * default currency for the locale is returned. If multiple currencies can be 18318 * recognized (for example if the currency sign is "$"), then this method 18319 * will prefer the one for the current locale. If multiple currencies can be 18320 * recognized, but none are used in the current locale, then the first currency 18321 * encountered will be used. This may produce random results, though the larger 18322 * currencies occur earlier in the list. For example, if the sign found in the 18323 * string is "$" and that is not the sign of the currency of the current locale 18324 * then the US dollar will be recognized, as it is the largest currency that uses 18325 * the "$" as its sign. 18326 * 18327 * @return {Currency|undefined} the currency instance for this amount, or 18328 * undefined if this INumber object is not of type currency 18329 */ 18330 getCurrency: function () { 18331 return this.currency; 18332 }, 18333 18334 /** 18335 * Return the value of this INumber object as a primitive number instance. 18336 * @return {number} the value of this number instance 18337 */ 18338 valueOf: function () { 18339 return this.value; 18340 } 18341 }; 18342 18343 18344 /*< NumFmt.js */ 18345 /* 18346 * NumFmt.js - Number formatter definition 18347 * 18348 * Copyright © 2012-2015, 2018 JEDLSoft 18349 * 18350 * Licensed under the Apache License, Version 2.0 (the "License"); 18351 * you may not use this file except in compliance with the License. 18352 * You may obtain a copy of the License at 18353 * 18354 * http://www.apache.org/licenses/LICENSE-2.0 18355 * 18356 * Unless required by applicable law or agreed to in writing, software 18357 * distributed under the License is distributed on an "AS IS" BASIS, 18358 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18359 * 18360 * See the License for the specific language governing permissions and 18361 * limitations under the License. 18362 */ 18363 18364 /* 18365 !depends 18366 ilib.js 18367 Locale.js 18368 LocaleInfo.js 18369 MathUtils.js 18370 Currency.js 18371 IString.js 18372 JSUtils.js 18373 */ 18374 18375 // !data localeinfo currency 18376 18377 18378 18379 /** 18380 * @class 18381 * Create a new number formatter instance. Locales differ in the way that digits 18382 * in a formatted number are grouped, in the way the decimal character is represented, 18383 * etc. Use this formatter to get it right for any locale.<p> 18384 * 18385 * This formatter can format plain numbers, currency amounts, and percentage amounts.<p> 18386 * 18387 * As with all formatters, the recommended 18388 * practice is to create one formatter and use it multiple times to format various 18389 * numbers.<p> 18390 * 18391 * The options can contain any of the following properties: 18392 * 18393 * <ul> 18394 * <li><i>locale</i> - use the conventions of the specified locale when figuring out how to 18395 * format a number. 18396 * <li><i>type</i> - the type of this formatter. Valid values are "number", "currency", or 18397 * "percentage". If this property is not specified, the default is "number". 18398 * <li><i>currency</i> - the ISO 4217 3-letter currency code to use when the formatter type 18399 * is "currency". This property is required for currency formatting. If the type property 18400 * is "currency" and the currency property is not specified, the constructor will throw a 18401 * an exception. 18402 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 18403 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 18404 * the integral part of the number. 18405 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 18406 * appear in the formatted output. If the number does not have enough fractional digits 18407 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 18408 * If the type of the formatter is "currency" and this 18409 * property is not specified, then the minimum fraction digits is set to the normal number 18410 * of digits used with that currency, which is almost always 0, 2, or 3 digits. 18411 * <li><i>significantDigits</i> - specify that max number of significant digits in the 18412 * formatted output. This applies before and after the decimal point. The amount is 18413 * rounded according to the rounding mode specified, or the rounding mode as given in 18414 * the locale information. If the significant digits and the max or min fraction digits 18415 * are both specified, this formatter will attempt to honour them both by choosing the 18416 * one that is smaller if there is a conflict. For example, if the max fraction digits 18417 * is 6 and the significant digits is 5 and the number to be formatted has a long 18418 * fraction, it will only format 5 digits. The default is "unlimited digits", which means 18419 * to format as many digits as the javascript engine can represent internally (usually 18420 * around 13-15 or so on a 64-bit machine). 18421 * <li><i>useNative</i> - the flag used to determaine whether to use the native script settings 18422 * for formatting the numbers . 18423 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 18424 * this property governs how the least significant digits are rounded to conform to that 18425 * maximum. The value of this property is a string with one of the following values: 18426 * <ul> 18427 * <li><i>up</i> - round away from zero 18428 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 18429 * <li><i>ceiling</i> - round towards positive infinity 18430 * <li><i>floor</i> - round towards negative infinity 18431 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 18432 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 18433 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 18434 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 18435 * </ul> 18436 * When the type of the formatter is "currency" and the <i>roundingMode</i> property is not 18437 * set, then the standard legal rounding rules for the locale are followed. If the type 18438 * is "number" or "percentage" and the <i>roundingMode</i> property is not set, then the 18439 * default mode is "halfdown".</i>. 18440 * 18441 * <li><i>style</i> - When the type of this formatter is "currency", the currency amount 18442 * can be formatted in the following styles: "common" and "iso". The common style is the 18443 * one commonly used in every day writing where the currency unit is represented using a 18444 * symbol. eg. "$57.35" for fifty-seven dollars and thirty five cents. The iso style is 18445 * the international style where the currency unit is represented using the ISO 4217 code. 18446 * eg. "USD 57.35" for the same amount. The default is "common" style if the style is 18447 * not specified.<p> 18448 * 18449 * When the type of this formatter is "number", the style can be one of the following: 18450 * <ul> 18451 * <li><i>standard - format a fully specified floating point number properly for the locale 18452 * <li><i>scientific</i> - use scientific notation for all numbers. That is, 1 integral 18453 * digit, followed by a number of fractional digits, followed by an "e" which denotes 18454 * exponentiation, followed digits which give the power of 10 in the exponent. 18455 * <li><i>native</i> - format a floating point number using the native digits and 18456 * formatting symbols for the script of the locale. 18457 * <li><i>nogrouping</i> - format a floating point number without grouping digits for 18458 * the integral portion of the number 18459 * </ul> 18460 * Note that if you specify a maximum number 18461 * of integral digits, the formatter with a standard style will give you standard 18462 * formatting for smaller numbers and scientific notation for larger numbers. The default 18463 * is standard style if this is not specified. 18464 * 18465 * <li><i>onLoad</i> - a callback function to call when the format data is fully 18466 * loaded. When the onLoad option is given, this class will attempt to 18467 * load any missing locale data using the ilib loader callback. 18468 * When the constructor is done (even if the data is already preassembled), the 18469 * onLoad function is called with the current instance as a parameter, so this 18470 * callback can be used with preassembled or dynamic loading or a mix of the two. 18471 * 18472 * <li>sync - tell whether to load any missing locale data synchronously or 18473 * asynchronously. If this option is given as "false", then the "onLoad" 18474 * callback must be given, as the instance returned from this constructor will 18475 * not be usable for a while. 18476 * 18477 * <li><i>loadParams</i> - an object containing parameters to pass to the 18478 * loader callback function when locale data is missing. The parameters are not 18479 * interpretted or modified in any way. They are simply passed along. The object 18480 * may contain any property/value pairs as long as the calling code is in 18481 * agreement with the loader callback function as to what those parameters mean. 18482 * </ul> 18483 * <p> 18484 * 18485 * 18486 * @constructor 18487 * @param {Object.<string,*>} options A set of options that govern how the formatter will behave 18488 */ 18489 var NumFmt = function (options) { 18490 var sync = true; 18491 this.locale = new Locale(); 18492 /** 18493 * @private 18494 * @type {string} 18495 */ 18496 this.type = "number"; 18497 var loadParams = undefined; 18498 18499 if (options) { 18500 if (options.locale) { 18501 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 18502 } 18503 18504 if (options.type) { 18505 if (options.type === 'number' || 18506 options.type === 'currency' || 18507 options.type === 'percentage') { 18508 this.type = options.type; 18509 } 18510 } 18511 18512 if (options.currency) { 18513 /** 18514 * @private 18515 * @type {string} 18516 */ 18517 this.currency = options.currency; 18518 } 18519 18520 if (typeof (options.maxFractionDigits) !== 'undefined') { 18521 /** 18522 * @private 18523 * @type {number|undefined} 18524 */ 18525 this.maxFractionDigits = Number(options.maxFractionDigits); 18526 } 18527 if (typeof (options.minFractionDigits) !== 'undefined') { 18528 /** 18529 * @private 18530 * @type {number|undefined} 18531 */ 18532 this.minFractionDigits = Number(options.minFractionDigits); 18533 // enforce the limits to avoid JS exceptions 18534 if (this.minFractionDigits < 0) { 18535 this.minFractionDigits = 0; 18536 } 18537 if (this.minFractionDigits > 20) { 18538 this.minFractionDigits = 20; 18539 } 18540 } 18541 if (typeof (options.significantDigits) !== 'undefined') { 18542 /** 18543 * @private 18544 * @type {number|undefined} 18545 */ 18546 this.significantDigits = Number(options.significantDigits); 18547 // enforce the limits to avoid JS exceptions 18548 if (this.significantDigits < 1) { 18549 this.significantDigits = 1; 18550 } 18551 if (this.significantDigits > 20) { 18552 this.significantDigits = 20; 18553 } 18554 } 18555 if (options.style) { 18556 /** 18557 * @private 18558 * @type {string} 18559 */ 18560 this.style = options.style; 18561 } 18562 if (typeof(options.useNative) === 'boolean') { 18563 /** 18564 * @private 18565 * @type {boolean} 18566 * */ 18567 this.useNative = options.useNative; 18568 } 18569 /** 18570 * @private 18571 * @type {string} 18572 */ 18573 this.roundingMode = options.roundingMode; 18574 18575 if (typeof(options.sync) === 'boolean') { 18576 sync = options.sync; 18577 } 18578 18579 loadParams = options.loadParams; 18580 } 18581 18582 /** 18583 * @private 18584 * @type {LocaleInfo|undefined} 18585 */ 18586 this.localeInfo = undefined; 18587 18588 new LocaleInfo(this.locale, { 18589 sync: sync, 18590 loadParams: loadParams, 18591 onLoad: ilib.bind(this, function (li) { 18592 /** 18593 * @private 18594 * @type {LocaleInfo|undefined} 18595 */ 18596 this.localeInfo = li; 18597 18598 if (this.type === "number") { 18599 this.templateNegative = new IString(this.localeInfo.getNegativeNumberFormat() || "-{n}"); 18600 } else if (this.type === "currency") { 18601 var templates; 18602 18603 if (!this.currency || typeof (this.currency) != 'string') { 18604 throw "A currency property is required in the options to the number formatter constructor when the type property is set to currency."; 18605 } 18606 18607 new Currency({ 18608 locale: this.locale, 18609 code: this.currency, 18610 sync: sync, 18611 loadParams: loadParams, 18612 onLoad: ilib.bind(this, function (cur) { 18613 this.currencyInfo = cur; 18614 if (this.style !== "common" && this.style !== "iso") { 18615 this.style = "common"; 18616 } 18617 18618 if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.minFractionDigits) !== 'number') { 18619 this.minFractionDigits = this.maxFractionDigits = this.currencyInfo.getFractionDigits(); 18620 } 18621 18622 templates = this.localeInfo.getCurrencyFormats(); 18623 this.template = new IString(templates[this.style] || templates.common); 18624 this.templateNegative = new IString(templates[this.style + "Negative"] || templates["commonNegative"]); 18625 this.sign = (this.style === "iso") ? this.currencyInfo.getCode() : this.currencyInfo.getSign(); 18626 18627 if (!this.roundingMode) { 18628 this.roundingMode = this.currencyInfo && this.currencyInfo.roundingMode; 18629 } 18630 18631 this._init(); 18632 18633 if (options && typeof (options.onLoad) === 'function') { 18634 options.onLoad(this); 18635 } 18636 }) 18637 }); 18638 return; 18639 } else if (this.type === "percentage") { 18640 this.template = new IString(this.localeInfo.getPercentageFormat() || "{n}%"); 18641 this.templateNegative = new IString(this.localeInfo.getNegativePercentageFormat() || this.localeInfo.getNegativeNumberFormat() + "%"); 18642 } 18643 18644 this._init(); 18645 18646 if (options && typeof (options.onLoad) === 'function') { 18647 options.onLoad(this); 18648 } 18649 }) 18650 }); 18651 }; 18652 18653 /** 18654 * Return an array of available locales that this formatter can format 18655 * @static 18656 * @return {Array.<Locale>|undefined} an array of available locales 18657 */ 18658 NumFmt.getAvailableLocales = function () { 18659 return undefined; 18660 }; 18661 18662 /** 18663 * @private 18664 * @const 18665 * @type string 18666 */ 18667 NumFmt.zeros = "0000000000000000000000000000000000000000000000000000000000000000000000"; 18668 18669 NumFmt.prototype = { 18670 /** 18671 * Return true if this formatter uses native digits to format the number. If the useNative 18672 * option is given to the constructor, then this flag will be honoured. If the useNative 18673 * option is not given to the constructor, this this formatter will use native digits if 18674 * the locale typically uses native digits. 18675 * 18676 * @return {boolean} true if this formatter will format with native digits, false otherwise 18677 */ 18678 getUseNative: function() { 18679 if (typeof(this.useNative) === "boolean") { 18680 return this.useNative; 18681 } 18682 return (this.localeInfo.getDigitsStyle() === "native"); 18683 }, 18684 18685 /** 18686 * @private 18687 */ 18688 _init: function () { 18689 if (this.maxFractionDigits < this.minFractionDigits) { 18690 this.minFractionDigits = this.maxFractionDigits; 18691 } 18692 18693 if (!this.roundingMode) { 18694 this.roundingMode = this.localeInfo.getRoundingMode(); 18695 } 18696 18697 if (!this.roundingMode) { 18698 this.roundingMode = "halfdown"; 18699 } 18700 18701 // set up the function, so we only have to figure it out once 18702 // and not every time we do format() 18703 this.round = MathUtils[this.roundingMode]; 18704 if (!this.round) { 18705 this.roundingMode = "halfdown"; 18706 this.round = MathUtils[this.roundingMode]; 18707 } 18708 18709 if (this.style === "nogrouping") { 18710 this.prigroupSize = this.secgroupSize = 0; 18711 } else { 18712 this.prigroupSize = this.localeInfo.getPrimaryGroupingDigits(); 18713 this.secgroupSize = this.localeInfo.getSecondaryGroupingDigits(); 18714 this.groupingSeparator = this.getUseNative() ? this.localeInfo.getNativeGroupingSeparator() : this.localeInfo.getGroupingSeparator(); 18715 } 18716 this.decimalSeparator = this.getUseNative() ? this.localeInfo.getNativeDecimalSeparator() : this.localeInfo.getDecimalSeparator(); 18717 18718 if (this.getUseNative()) { 18719 var nd = this.localeInfo.getNativeDigits() || this.localeInfo.getDigits(); 18720 if (nd) { 18721 this.digits = nd.split(""); 18722 } 18723 } 18724 18725 this.exponentSymbol = this.localeInfo.getExponential() || "e"; 18726 }, 18727 18728 /** 18729 * Apply the constraints used in the current formatter to the given number. This will 18730 * will apply the maxFractionDigits, significantDigits, and rounding mode 18731 * constraints and return the result. The result is further 18732 * manipulated in the format method to produce the final formatted number string. 18733 * This method is intended for use by code that needs to use the same number that 18734 * this formatter instance uses for formatting before that number is turned into a 18735 * formatted string. 18736 * 18737 * @param {number} num the number to constrain 18738 * @returns {number} the number with the constraints applied to it 18739 */ 18740 constrain: function(num) { 18741 var parts = ("" + num).split("."), 18742 result = num; 18743 18744 // only apply the either significantDigits or the maxFractionDigits -- whichever results in a shorter fractional part 18745 if ((typeof(this.significantDigits) !== 'undefined' && this.significantDigits > 0) && 18746 (typeof(this.maxFractionDigits) === 'undefined' || this.maxFractionDigits < 0 || 18747 parts[0].length + this.maxFractionDigits > this.significantDigits)) { 18748 result = MathUtils.significant(result, this.significantDigits, this.round); 18749 } 18750 18751 if (typeof(this.maxFractionDigits) !== 'undefined' && this.maxFractionDigits > -1) { 18752 result = MathUtils.shiftDecimal(this.round(MathUtils.shiftDecimal(result, this.maxFractionDigits)), -this.maxFractionDigits); 18753 } 18754 18755 return result; 18756 }, 18757 18758 /** 18759 * Format the number using scientific notation as a positive number. Negative 18760 * formatting to be applied later. 18761 * @private 18762 * @param {number} num the number to format 18763 * @return {string} the formatted number 18764 */ 18765 _formatScientific: function (num) { 18766 var n = new Number(num); 18767 var formatted; 18768 18769 var str = n.toExponential(), 18770 parts = str.split("e"), 18771 significant = parts[0], 18772 exponent = parts[1], 18773 numparts, 18774 integral, 18775 fraction; 18776 18777 if (this.maxFractionDigits > 0 || this.significantDigits > 0) { 18778 // if there is a max fraction digits setting, round the fraction to 18779 // the right length first by dividing or multiplying by powers of 10. 18780 // manipulate the fraction digits so as to 18781 // avoid the rounding errors of floating point numbers 18782 var maxDigits = (this.maxFractionDigits || 25) + 1; 18783 if (this.significantDigits > 0) { 18784 maxDigits = Math.min(maxDigits, this.significantDigits); 18785 } 18786 significant = MathUtils.significant(Number(significant), maxDigits, this.round); 18787 } 18788 numparts = ("" + significant).split("."); 18789 integral = numparts[0]; 18790 fraction = numparts[1]; 18791 18792 if (typeof(this.maxFractionDigits) !== 'undefined') { 18793 fraction = fraction.substring(0, this.maxFractionDigits); 18794 } 18795 if (typeof(this.minFractionDigits) !== 'undefined') { 18796 fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); 18797 } 18798 formatted = integral; 18799 if (fraction.length) { 18800 formatted += this.decimalSeparator + fraction; 18801 } 18802 formatted += this.exponentSymbol + exponent; 18803 return formatted; 18804 }, 18805 18806 /** 18807 * Formats the number as a positive number. Negative formatting to be applied later. 18808 * @private 18809 * @param {number} num the number to format 18810 * @return {string} the formatted number 18811 */ 18812 _formatStandard: function (num) { 18813 var i; 18814 var k; 18815 18816 var parts, 18817 integral, 18818 fraction, 18819 cycle, 18820 formatted; 18821 18822 num = Math.abs(this.constrain(num)); 18823 18824 parts = ("" + num).split("."); 18825 integral = parts[0].toString(); 18826 fraction = parts[1]; 18827 18828 if (this.minFractionDigits > 0) { 18829 fraction = JSUtils.pad(fraction || "", this.minFractionDigits, true); 18830 } 18831 18832 if (this.secgroupSize > 0) { 18833 if (integral.length > this.prigroupSize) { 18834 var size1 = this.prigroupSize; 18835 var size2 = integral.length; 18836 var size3 = size2 - size1; 18837 integral = integral.slice(0, size3) + this.groupingSeparator + integral.slice(size3); 18838 var num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 18839 k = num_sec.length; 18840 while (k > this.secgroupSize) { 18841 var secsize1 = this.secgroupSize; 18842 var secsize2 = num_sec.length; 18843 var secsize3 = secsize2 - secsize1; 18844 integral = integral.slice(0, secsize3) + this.groupingSeparator + integral.slice(secsize3); 18845 num_sec = integral.substring(0, integral.indexOf(this.groupingSeparator)); 18846 k = num_sec.length; 18847 } 18848 } 18849 18850 formatted = integral; 18851 } else if (this.prigroupSize !== 0) { 18852 cycle = MathUtils.mod(integral.length - 1, this.prigroupSize); 18853 18854 formatted = ""; 18855 18856 for (i = 0; i < integral.length - 1; i++) { 18857 formatted += integral.charAt(i); 18858 if (cycle === 0) { 18859 formatted += this.groupingSeparator; 18860 } 18861 cycle = MathUtils.mod(cycle - 1, this.prigroupSize); 18862 } 18863 formatted += integral.charAt(integral.length - 1); 18864 } else { 18865 formatted = integral; 18866 } 18867 18868 if (fraction && 18869 ((typeof(this.maxFractionDigits) === 'undefined' && typeof(this.significantDigits) === 'undefined') || 18870 this.maxFractionDigits > 0 || this.significantDigits > 0)) { 18871 formatted += this.decimalSeparator; 18872 formatted += fraction; 18873 } 18874 18875 if (this.digits) { 18876 formatted = JSUtils.mapString(formatted, this.digits); 18877 } 18878 18879 return formatted; 18880 }, 18881 18882 /** 18883 * Format a number according to the settings of this number formatter instance. 18884 * @param num {number|string|INumber|Number} a floating point number to format 18885 * @return {string} a string containing the formatted number 18886 */ 18887 format: function (num) { 18888 var formatted, n; 18889 18890 if (typeof (num) === 'undefined') { 18891 return ""; 18892 } 18893 18894 // convert to a real primitive number type 18895 n = Number(num); 18896 18897 if (this.type === "number") { 18898 formatted = (this.style === "scientific") ? 18899 this._formatScientific(n) : 18900 this._formatStandard(n); 18901 18902 if (num < 0) { 18903 formatted = this.templateNegative.format({n: formatted}); 18904 } 18905 } else { 18906 formatted = this._formatStandard(n); 18907 var template = (n < 0) ? this.templateNegative : this.template; 18908 formatted = template.format({ 18909 n: formatted, 18910 s: this.sign 18911 }); 18912 } 18913 18914 return formatted; 18915 }, 18916 18917 /** 18918 * Return the type of formatter. Valid values are "number", "currency", and 18919 * "percentage". 18920 * 18921 * @return {string} the type of formatter 18922 */ 18923 getType: function () { 18924 return this.type; 18925 }, 18926 18927 /** 18928 * Return the locale for this formatter instance. 18929 * @return {Locale} the locale instance for this formatter 18930 */ 18931 getLocale: function () { 18932 return this.locale; 18933 }, 18934 18935 /** 18936 * Returns true if this formatter groups together digits in the integral 18937 * portion of a number, based on the options set up in the constructor. In 18938 * most western European cultures, this means separating every 3 digits 18939 * of the integral portion of a number with a particular character. 18940 * 18941 * @return {boolean} true if this formatter groups digits in the integral 18942 * portion of the number 18943 */ 18944 isGroupingUsed: function () { 18945 return (this.groupingSeparator !== 'undefined' && this.groupingSeparator.length > 0); 18946 }, 18947 18948 /** 18949 * Returns the maximum fraction digits set up in the constructor. 18950 * 18951 * @return {number} the maximum number of fractional digits this 18952 * formatter will format, or -1 for no maximum 18953 */ 18954 getMaxFractionDigits: function () { 18955 return typeof (this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : -1; 18956 }, 18957 18958 /** 18959 * Returns the minimum fraction digits set up in the constructor. If 18960 * the formatter has the type "currency", then the minimum fraction 18961 * digits is the amount of digits that is standard for the currency 18962 * in question unless overridden in the options to the constructor. 18963 * 18964 * @return {number} the minimum number of fractional digits this 18965 * formatter will format, or -1 for no minimum 18966 */ 18967 getMinFractionDigits: function () { 18968 return typeof (this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : -1; 18969 }, 18970 18971 /** 18972 * Returns the significant digits set up in the constructor. 18973 * 18974 * @return {number} the number of significant digits this 18975 * formatter will format, or -1 for no minimum 18976 */ 18977 getSignificantDigits: function () { 18978 return typeof (this.significantDigits) !== 'undefined' ? this.significantDigits : -1; 18979 }, 18980 18981 /** 18982 * Returns the ISO 4217 code for the currency that this formatter formats. 18983 * IF the typeof this formatter is not "currency", then this method will 18984 * return undefined. 18985 * 18986 * @return {string} the ISO 4217 code for the currency that this formatter 18987 * formats, or undefined if this not a currency formatter 18988 */ 18989 getCurrency: function () { 18990 return this.currencyInfo && this.currencyInfo.getCode(); 18991 }, 18992 18993 /** 18994 * Returns the rounding mode set up in the constructor. The rounding mode 18995 * controls how numbers are rounded when the integral or fraction digits 18996 * of a number are limited. 18997 * 18998 * @return {string} the name of the rounding mode used in this formatter 18999 */ 19000 getRoundingMode: function () { 19001 return this.roundingMode; 19002 }, 19003 19004 /** 19005 * If this formatter is a currency formatter, then the style determines how the 19006 * currency is denoted in the formatted output. This method returns the style 19007 * that this formatter will produce. (See the constructor comment for more about 19008 * the styles.) 19009 * @return {string} the name of the style this formatter will use to format 19010 * currency amounts, or "undefined" if this formatter is not a currency formatter 19011 */ 19012 getStyle: function () { 19013 return this.style; 19014 } 19015 }; 19016 19017 19018 /*< ScriptInfo.js */ 19019 /* 19020 * ScriptInfo.js - information about scripts 19021 * 19022 * Copyright © 2012-2018, JEDLSoft 19023 * 19024 * Licensed under the Apache License, Version 2.0 (the "License"); 19025 * you may not use this file except in compliance with the License. 19026 * You may obtain a copy of the License at 19027 * 19028 * http://www.apache.org/licenses/LICENSE-2.0 19029 * 19030 * Unless required by applicable law or agreed to in writing, software 19031 * distributed under the License is distributed on an "AS IS" BASIS, 19032 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19033 * 19034 * See the License for the specific language governing permissions and 19035 * limitations under the License. 19036 */ 19037 19038 // !depends ilib.js Utils.js 19039 19040 // !data scripts 19041 19042 19043 /** 19044 * @class 19045 * Create a new script info instance. This class encodes information about 19046 * scripts, which are sets of characters used in a writing system.<p> 19047 * 19048 * The options object may contain any of the following properties: 19049 * 19050 * <ul> 19051 * <li><i>onLoad</i> - a callback function to call when the script info object is fully 19052 * loaded. When the onLoad option is given, the script info object will attempt to 19053 * load any missing locale data using the ilib loader callback. 19054 * When the constructor is done (even if the data is already preassembled), the 19055 * onLoad function is called with the current instance as a parameter, so this 19056 * callback can be used with preassembled or dynamic loading or a mix of the two. 19057 * 19058 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 19059 * asynchronously. If this option is given as "false", then the "onLoad" 19060 * callback must be given, as the instance returned from this constructor will 19061 * not be usable for a while. 19062 * 19063 * <li><i>loadParams</i> - an object containing parameters to pass to the 19064 * loader callback function when locale data is missing. The parameters are not 19065 * interpretted or modified in any way. They are simply passed along. The object 19066 * may contain any property/value pairs as long as the calling code is in 19067 * agreement with the loader callback function as to what those parameters mean. 19068 * </ul> 19069 * 19070 * 19071 * @constructor 19072 * @param {string} script The ISO 15924 4-letter identifier for the script 19073 * @param {Object=} options parameters to initialize this instance 19074 */ 19075 var ScriptInfo = function(script, options) { 19076 var sync = true, 19077 loadParams = undefined; 19078 19079 this.script = script; 19080 19081 if (options) { 19082 if (typeof(options.sync) !== 'undefined') { 19083 sync = !!options.sync; 19084 } 19085 19086 if (typeof(options.loadParams) !== 'undefined') { 19087 loadParams = options.loadParams; 19088 } 19089 } 19090 19091 if (!ilib.data.scripts) { 19092 Utils.loadData({ 19093 object: "ScriptInfo", 19094 locale: "-", 19095 name: "scripts.json", 19096 sync: sync, 19097 loadParams: loadParams, 19098 callback: ilib.bind(this, function (info) { 19099 if (!info) { 19100 info = {"Latn":{"nb":215,"nm":"Latin","lid":"Latin","rtl":false,"ime":false,"casing":true}}; 19101 var spec = this.locale.getSpec().replace(/-/g, "_"); 19102 ilib.data.cache.ScriptInfo[spec] = info; 19103 } 19104 ilib.data.scripts = info; 19105 this.info = script && ilib.data.scripts[script]; 19106 if (options && typeof(options.onLoad) === 'function') { 19107 options.onLoad(this); 19108 } 19109 }) 19110 }); 19111 } else { 19112 this.info = ilib.data.scripts[script]; 19113 if (options && typeof(options.onLoad) === 'function') { 19114 options.onLoad(this); 19115 } 19116 } 19117 }; 19118 19119 /** 19120 * @private 19121 */ 19122 ScriptInfo._getScriptsArray = function() { 19123 var ret = [], 19124 script = undefined, 19125 scripts = ilib.data.scripts; 19126 19127 for (script in scripts) { 19128 if (script && scripts[script]) { 19129 ret.push(script); 19130 } 19131 } 19132 19133 return ret; 19134 }; 19135 19136 /** 19137 * Return an array of all ISO 15924 4-letter identifier script identifiers that 19138 * this copy of ilib knows about. 19139 * @static 19140 * @param {boolean} sync whether to find the available ids synchronously (true) or asynchronously (false) 19141 * @param {Object} loadParams arbitrary object full of properties to pass to the loader 19142 * @param {function(Array.<string>)} onLoad callback function to call when the data is finished loading 19143 * @return {Array.<string>} an array of all script identifiers that this copy of 19144 * ilib knows about 19145 */ 19146 ScriptInfo.getAllScripts = function(sync, loadParams, onLoad) { 19147 if (!ilib.data.scripts) { 19148 Utils.loadData({ 19149 object: "ScriptInfo", 19150 locale: "-", 19151 name: "scripts.json", 19152 sync: sync, 19153 loadParams: loadParams, 19154 callback: ilib.bind(this, function (info) { 19155 ilib.data.scripts = info; 19156 19157 if (typeof(onLoad) === 'function') { 19158 onLoad(ScriptInfo._getScriptsArray()); 19159 } 19160 }) 19161 }); 19162 } else { 19163 if (typeof(onLoad) === 'function') { 19164 onLoad(ScriptInfo._getScriptsArray()); 19165 } 19166 } 19167 19168 return ScriptInfo._getScriptsArray(); 19169 }; 19170 19171 ScriptInfo.prototype = { 19172 /** 19173 * Return the 4-letter ISO 15924 identifier associated 19174 * with this script. 19175 * @return {string} the 4-letter ISO code for this script 19176 */ 19177 getCode: function () { 19178 return this.info && this.script; 19179 }, 19180 19181 /** 19182 * Get the ISO 15924 code number associated with this 19183 * script. 19184 * 19185 * @return {number} the ISO 15924 code number 19186 */ 19187 getCodeNumber: function () { 19188 return this.info && this.info.nb || 0; 19189 }, 19190 19191 /** 19192 * Get the name of this script in English. 19193 * 19194 * @return {string} the name of this script in English 19195 */ 19196 getName: function () { 19197 return this.info && this.info.nm; 19198 }, 19199 19200 /** 19201 * Get the long identifier assciated with this script. 19202 * 19203 * @return {string} the long identifier of this script 19204 */ 19205 getLongCode: function () { 19206 return this.info && this.info.lid; 19207 }, 19208 19209 /** 19210 * Return the usual direction that text in this script is written 19211 * in. Possible return values are "rtl" for right-to-left, 19212 * "ltr" for left-to-right, and "ttb" for top-to-bottom. 19213 * 19214 * @return {string} the usual direction that text in this script is 19215 * written in 19216 */ 19217 getScriptDirection: function() { 19218 return (this.info && typeof(this.info.rtl) !== 'undefined' && this.info.rtl) ? "rtl" : "ltr"; 19219 }, 19220 19221 /** 19222 * Return true if this script typically requires an input method engine 19223 * to enter its characters. 19224 * 19225 * @return {boolean} true if this script typically requires an IME 19226 */ 19227 getNeedsIME: function () { 19228 return this.info && this.info.ime ? true : false; // converts undefined to false 19229 }, 19230 19231 /** 19232 * Return true if this script uses lower- and upper-case characters. 19233 * 19234 * @return {boolean} true if this script uses letter case 19235 */ 19236 getCasing: function () { 19237 return this.info && this.info.casing ? true : false; // converts undefined to false 19238 } 19239 }; 19240 19241 19242 /*< DurationFmt.js */ 19243 /* 19244 * DurFmt.js - Date formatter definition 19245 * 19246 * Copyright © 2012-2015, 2018, JEDLSoft 19247 * 19248 * Licensed under the Apache License, Version 2.0 (the "License"); 19249 * you may not use this file except in compliance with the License. 19250 * You may obtain a copy of the License at 19251 * 19252 * http://www.apache.org/licenses/LICENSE-2.0 19253 * 19254 * Unless required by applicable law or agreed to in writing, software 19255 * distributed under the License is distributed on an "AS IS" BASIS, 19256 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19257 * 19258 * See the License for the specific language governing permissions and 19259 * limitations under the License. 19260 */ 19261 19262 /* 19263 !depends 19264 ilib.js 19265 Locale.js 19266 DateFmt.js 19267 IString.js 19268 ResBundle.js 19269 LocaleInfo.js 19270 JSUtils.js 19271 ScriptInfo.js 19272 */ 19273 19274 // !data dateformats sysres 19275 // !resbundle sysres 19276 19277 19278 /** 19279 * @class 19280 * Create a new duration formatter instance. The duration formatter is immutable once 19281 * it is created, but can format as many different durations as needed with the same 19282 * options. Create different duration formatter instances for different purposes 19283 * and then keep them cached for use later if you have more than one duration to 19284 * format.<p> 19285 * 19286 * Duration formatters format lengths of time. The duration formatter is meant to format 19287 * durations of such things as the length of a song or a movie or a meeting, or the 19288 * current position in that song or movie while playing it. If you wish to format a 19289 * period of time that has a specific start and end date/time, then use a 19290 * [DateRngFmt] instance instead and call its format method.<p> 19291 * 19292 * The options may contain any of the following properties: 19293 * 19294 * <ul> 19295 * <li><i>locale</i> - locale to use when formatting the duration. If the locale is 19296 * not specified, then the default locale of the app or web page will be used. 19297 * 19298 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 19299 * formatted string. 19300 * 19301 * <ul> 19302 * <li><i>short</i> - use a short representation of the duration. This is the most compact format possible for the locale. eg. 1y 1m 1w 1d 1:01:01 19303 * <li><i>medium</i> - use a medium length representation of the duration. This is a slightly longer format. eg. 1 yr 1 mo 1 wk 1 dy 1 hr 1 mi 1 se 19304 * <li><i>long</i> - use a long representation of the duration. This is a fully specified format, but some of the textual 19305 * parts may still be abbreviated. eg. 1 yr 1 mo 1 wk 1 day 1 hr 1 min 1 sec 19306 * <li><i>full</i> - use a full representation of the duration. This is a fully specified format where all the textual 19307 * parts are spelled out completely. eg. 1 year, 1 month, 1 week, 1 day, 1 hour, 1 minute and 1 second 19308 * </ul> 19309 * 19310 * <li><i>style<i> - whether hours, minutes, and seconds should be formatted as a text string 19311 * or as a regular time as on a clock. eg. text is "1 hour, 15 minutes", whereas clock is "1:15:00". Valid 19312 * values for this property are "text" or "clock". Default if this property is not specified 19313 * is "text". 19314 * 19315 *<li><i>useNative</i> - the flag used to determaine whether to use the native script settings 19316 * for formatting the numbers . 19317 * 19318 * <li><i>onLoad</i> - a callback function to call when the format data is fully 19319 * loaded. When the onLoad option is given, this class will attempt to 19320 * load any missing locale data using the ilib loader callback. 19321 * When the constructor is done (even if the data is already preassembled), the 19322 * onLoad function is called with the current instance as a parameter, so this 19323 * callback can be used with preassembled or dynamic loading or a mix of the two. 19324 * 19325 * <li>sync - tell whether to load any missing locale data synchronously or 19326 * asynchronously. If this option is given as "false", then the "onLoad" 19327 * callback must be given, as the instance returned from this constructor will 19328 * not be usable for a while. 19329 * 19330 * <li><i>loadParams</i> - an object containing parameters to pass to the 19331 * loader callback function when locale data is missing. The parameters are not 19332 * interpretted or modified in any way. They are simply passed along. The object 19333 * may contain any property/value pairs as long as the calling code is in 19334 * agreement with the loader callback function as to what those parameters mean. 19335 * </ul> 19336 * <p> 19337 * 19338 * 19339 * @constructor 19340 * @param {?Object} options options governing the way this date formatter instance works 19341 */ 19342 var DurationFmt = function(options) { 19343 var sync = true; 19344 var loadParams = undefined; 19345 19346 this.locale = new Locale(); 19347 this.length = "short"; 19348 this.style = "text"; 19349 19350 if (options) { 19351 if (options.locale) { 19352 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 19353 } 19354 19355 if (options.length) { 19356 if (options.length === 'short' || 19357 options.length === 'medium' || 19358 options.length === 'long' || 19359 options.length === 'full') { 19360 this.length = options.length; 19361 } 19362 } 19363 19364 if (options.style) { 19365 if (options.style === 'text' || options.style === 'clock') { 19366 this.style = options.style; 19367 } 19368 } 19369 19370 if (typeof(options.sync) !== 'undefined') { 19371 sync = !!options.sync; 19372 } 19373 19374 if (typeof(options.useNative) === 'boolean') { 19375 this.useNative = options.useNative; 19376 } 19377 19378 loadParams = options.loadParams; 19379 } 19380 options = options || {sync: true}; 19381 19382 new LocaleInfo(this.locale, { 19383 sync: sync, 19384 loadParams: loadParams, 19385 onLoad: ilib.bind(this, function (li) { 19386 this.script = li.getScript(); 19387 new ResBundle({ 19388 locale: this.locale, 19389 name: "sysres", 19390 sync: sync, 19391 loadParams: loadParams, 19392 onLoad: ilib.bind(this, function (sysres) { 19393 IString.loadPlurals(sync, this.locale, loadParams, ilib.bind(this, function() { 19394 if (this.length === 'medium' && !(this.script === 'Latn' || this.script ==='Grek' || this.script ==='Cyrl')) { 19395 this.length = 'short'; 19396 } 19397 switch (this.length) { 19398 case 'short': 19399 this.components = { 19400 year: sysres.getString("#{num}y"), 19401 month: sysres.getString("#{num}m", "durationShortMonths"), 19402 week: sysres.getString("#{num}w"), 19403 day: sysres.getString("#{num}d"), 19404 hour: sysres.getString("#{num}h"), 19405 minute: sysres.getString("#{num}m", "durationShortMinutes"), 19406 second: sysres.getString("#{num}s"), 19407 millisecond: sysres.getString("#{num}m", "durationShortMillis"), 19408 separator: sysres.getString(" ", "separatorShort"), 19409 finalSeparator: "" // not used at this length 19410 }; 19411 break; 19412 19413 case 'medium': 19414 this.components = { 19415 year: sysres.getString("1#1 yr|#{num} yrs", "durationMediumYears"), 19416 month: sysres.getString("1#1 mo|#{num} mos"), 19417 week: sysres.getString("1#1 wk|#{num} wks", "durationMediumWeeks"), 19418 day: sysres.getString("1#1 dy|#{num} dys"), 19419 hour: sysres.getString("1#1 hr|#{num} hrs", "durationMediumHours"), 19420 minute: sysres.getString("1#1 mi|#{num} min"), 19421 second: sysres.getString("1#1 se|#{num} sec"), 19422 millisecond: sysres.getString("#{num} ms", "durationMediumMillis"), 19423 separator: sysres.getString(" ", "separatorMedium"), 19424 finalSeparator: "" // not used at this length 19425 }; 19426 break; 19427 19428 case 'long': 19429 this.components = { 19430 year: sysres.getString("1#1 yr|#{num} yrs"), 19431 month: sysres.getString("1#1 mon|#{num} mons"), 19432 week: sysres.getString("1#1 wk|#{num} wks"), 19433 day: sysres.getString("1#1 day|#{num} days", "durationLongDays"), 19434 hour: sysres.getString("1#1 hr|#{num} hrs"), 19435 minute: sysres.getString("1#1 min|#{num} min"), 19436 second: sysres.getString("1#1 sec|#{num} sec"), 19437 millisecond: sysres.getString("#{num} ms"), 19438 separator: sysres.getString(", ", "separatorLong"), 19439 finalSeparator: "" // not used at this length 19440 }; 19441 break; 19442 19443 case 'full': 19444 this.components = { 19445 year: sysres.getString("1#1 year|#{num} years"), 19446 month: sysres.getString("1#1 month|#{num} months"), 19447 week: sysres.getString("1#1 week|#{num} weeks"), 19448 day: sysres.getString("1#1 day|#{num} days"), 19449 hour: sysres.getString("1#1 hour|#{num} hours"), 19450 minute: sysres.getString("1#1 minute|#{num} minutes"), 19451 second: sysres.getString("1#1 second|#{num} seconds"), 19452 millisecond: sysres.getString("1#1 millisecond|#{num} milliseconds"), 19453 separator: sysres.getString(", ", "separatorFull"), 19454 finalSeparator: sysres.getString(" and ", "finalSeparatorFull") 19455 }; 19456 break; 19457 } 19458 19459 if (this.style === 'clock') { 19460 new DateFmt({ 19461 locale: this.locale, 19462 calendar: "gregorian", 19463 type: "time", 19464 time: "ms", 19465 sync: sync, 19466 loadParams: loadParams, 19467 useNative: this.useNative, 19468 onLoad: ilib.bind(this, function (fmtMS) { 19469 this.timeFmtMS = fmtMS; 19470 new DateFmt({ 19471 locale: this.locale, 19472 calendar: "gregorian", 19473 type: "time", 19474 time: "hm", 19475 sync: sync, 19476 loadParams: loadParams, 19477 useNative: this.useNative, 19478 onLoad: ilib.bind(this, function (fmtHM) { 19479 this.timeFmtHM = fmtHM; 19480 new DateFmt({ 19481 locale: this.locale, 19482 calendar: "gregorian", 19483 type: "time", 19484 time: "hms", 19485 sync: sync, 19486 loadParams: loadParams, 19487 useNative: this.useNative, 19488 onLoad: ilib.bind(this, function (fmtHMS) { 19489 this.timeFmtHMS = fmtHMS; 19490 19491 // munge with the template to make sure that the hours are not formatted mod 12 19492 this.timeFmtHM.template = this.timeFmtHM.template.replace(/hh?/, 'H'); 19493 this.timeFmtHM.templateArr = this.timeFmtHM._tokenize(this.timeFmtHM.template); 19494 this.timeFmtHMS.template = this.timeFmtHMS.template.replace(/hh?/, 'H'); 19495 this.timeFmtHMS.templateArr = this.timeFmtHMS._tokenize(this.timeFmtHMS.template); 19496 19497 this._init(this.timeFmtHM.locinfo, options); 19498 }) 19499 }); 19500 }) 19501 }); 19502 }) 19503 }); 19504 return; 19505 } 19506 this._init(li, options); 19507 })); 19508 }) 19509 }); 19510 }) 19511 }); 19512 }; 19513 19514 /** 19515 * @private 19516 * @static 19517 */ 19518 DurationFmt.complist = { 19519 "text": ["year", "month", "week", "day", "hour", "minute", "second", "millisecond"], 19520 "clock": ["year", "month", "week", "day"] 19521 }; 19522 19523 /** 19524 * @private 19525 */ 19526 DurationFmt.prototype._mapDigits = function(str) { 19527 if (this.useNative && this.digits) { 19528 return JSUtils.mapString(str.toString(), this.digits); 19529 } 19530 return str; 19531 }; 19532 19533 /** 19534 * @private 19535 * @param {LocaleInfo} locinfo 19536 * @param {Object|undefined} options 19537 */ 19538 DurationFmt.prototype._init = function(locinfo, options) { 19539 var digits; 19540 new ScriptInfo(locinfo.getScript(), { 19541 sync: options.sync, 19542 loadParams: options.loadParams, 19543 onLoad: ilib.bind(this, function(scriptInfo) { 19544 this.scriptDirection = scriptInfo.getScriptDirection(); 19545 19546 if (typeof(this.useNative) === 'boolean') { 19547 // if the caller explicitly said to use native or not, honour that despite what the locale data says... 19548 if (this.useNative) { 19549 digits = locinfo.getNativeDigits(); 19550 if (digits) { 19551 this.digits = digits; 19552 } 19553 } 19554 } else if (locinfo.getDigitsStyle() === "native") { 19555 // else if the locale usually uses native digits, then use them 19556 digits = locinfo.getNativeDigits(); 19557 if (digits) { 19558 this.useNative = true; 19559 this.digits = digits; 19560 } 19561 } // else use western digits always 19562 19563 if (typeof(options.onLoad) === 'function') { 19564 options.onLoad(this); 19565 } 19566 }) 19567 }); 19568 }; 19569 19570 /** 19571 * Format a duration according to the format template of this formatter instance.<p> 19572 * 19573 * The components parameter should be an object that contains any or all of these 19574 * numeric properties: 19575 * 19576 * <ul> 19577 * <li>year 19578 * <li>month 19579 * <li>week 19580 * <li>day 19581 * <li>hour 19582 * <li>minute 19583 * <li>second 19584 * </ul> 19585 * <p> 19586 * 19587 * When a property is left out of the components parameter or has a value of 0, it will not 19588 * be formatted into the output string, except for times that include 0 minutes and 0 seconds. 19589 * 19590 * This formatter will not ensure that numbers for each component property is within the 19591 * valid range for that component. This allows you to format durations that are longer 19592 * than normal range. For example, you could format a duration has being "33 hours" rather 19593 * than "1 day, 9 hours". 19594 * 19595 * @param {Object} components date/time components to be formatted into a duration string 19596 * @return {IString} a string with the duration formatted according to the style and 19597 * locale set up for this formatter instance. If the components parameter is empty or 19598 * undefined, an empty string is returned. 19599 */ 19600 DurationFmt.prototype.format = function (components) { 19601 var i, list, fmt, secondlast = true, str = ""; 19602 19603 list = DurationFmt.complist[this.style]; 19604 //for (i = 0; i < list.length; i++) { 19605 for (i = list.length-1; i >= 0; i--) { 19606 //console.log("Now dealing with " + list[i]); 19607 if (typeof(components[list[i]]) !== 'undefined' && components[list[i]] != 0) { 19608 if (str.length > 0) { 19609 str = ((this.length === 'full' && secondlast) ? this.components.finalSeparator : this.components.separator) + str; 19610 secondlast = false; 19611 } 19612 str = this.components[list[i]].formatChoice(components[list[i]], {num: this._mapDigits(components[list[i]])}) + str; 19613 } 19614 } 19615 19616 if (this.style === 'clock') { 19617 if (typeof(components.hour) !== 'undefined') { 19618 fmt = (typeof(components.second) !== 'undefined') ? this.timeFmtHMS : this.timeFmtHM; 19619 } else { 19620 fmt = this.timeFmtMS; 19621 } 19622 19623 if (str.length > 0) { 19624 str += this.components.separator; 19625 } 19626 str += fmt._formatTemplate(components, fmt.templateArr); 19627 } 19628 19629 if (this.scriptDirection === 'rtl') { 19630 str = "\u200F" + str; 19631 } 19632 return new IString(str); 19633 }; 19634 19635 /** 19636 * Return the locale that was used to construct this duration formatter object. If the 19637 * locale was not given as parameter to the constructor, this method returns the default 19638 * locale of the system. 19639 * 19640 * @return {Locale} locale that this duration formatter was constructed with 19641 */ 19642 DurationFmt.prototype.getLocale = function () { 19643 return this.locale; 19644 }; 19645 19646 /** 19647 * Return the length that was used to construct this duration formatter object. If the 19648 * length was not given as parameter to the constructor, this method returns the default 19649 * length. Valid values are "short", "medium", "long", and "full". 19650 * 19651 * @return {string} length that this duration formatter was constructed with 19652 */ 19653 DurationFmt.prototype.getLength = function () { 19654 return this.length; 19655 }; 19656 19657 /** 19658 * Return the style that was used to construct this duration formatter object. Returns 19659 * one of "text" or "clock". 19660 * 19661 * @return {string} style that this duration formatter was constructed with 19662 */ 19663 DurationFmt.prototype.getStyle = function () { 19664 return this.style; 19665 }; 19666 19667 19668 19669 /*< isAlpha.js */ 19670 /* 19671 * ctype.islpha.js - Character type is alphabetic 19672 * 19673 * Copyright © 2012-2015, JEDLSoft 19674 * 19675 * Licensed under the Apache License, Version 2.0 (the "License"); 19676 * you may not use this file except in compliance with the License. 19677 * You may obtain a copy of the License at 19678 * 19679 * http://www.apache.org/licenses/LICENSE-2.0 19680 * 19681 * Unless required by applicable law or agreed to in writing, software 19682 * distributed under the License is distributed on an "AS IS" BASIS, 19683 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19684 * 19685 * See the License for the specific language governing permissions and 19686 * limitations under the License. 19687 */ 19688 19689 // !depends CType.js IString.js ilib.js 19690 19691 // !data ctype_l 19692 19693 19694 /** 19695 * Return whether or not the first character is alphabetic.<p> 19696 * 19697 * @static 19698 * @param {string|IString|number} ch character or code point to examine 19699 * @return {boolean} true if the first character is alphabetic. 19700 */ 19701 var isAlpha = function (ch) { 19702 var num; 19703 switch (typeof(ch)) { 19704 case 'number': 19705 num = ch; 19706 break; 19707 case 'string': 19708 num = IString.toCodePoint(ch, 0); 19709 break; 19710 case 'undefined': 19711 return false; 19712 default: 19713 num = ch._toCodePoint(0); 19714 break; 19715 } 19716 return ilib.data.ctype_l ? 19717 (CType._inRange(num, 'Lu', ilib.data.ctype_l) || 19718 CType._inRange(num, 'Ll', ilib.data.ctype_l) || 19719 CType._inRange(num, 'Lt', ilib.data.ctype_l) || 19720 CType._inRange(num, 'Lm', ilib.data.ctype_l) || 19721 CType._inRange(num, 'Lo', ilib.data.ctype_l)) : 19722 ((num >= 0x41 && num <= 0x5A) || (num >= 0x61 && num <= 0x7A)); 19723 }; 19724 19725 /** 19726 * @protected 19727 * @param {boolean} sync 19728 * @param {Object|undefined} loadParams 19729 * @param {function(*)|undefined} onLoad 19730 */ 19731 isAlpha._init = function (sync, loadParams, onLoad) { 19732 CType._load("ctype_l", sync, loadParams, onLoad); 19733 }; 19734 19735 19736 /*< isAlnum.js */ 19737 /* 19738 * isAlnum.js - Character type is alphanumeric 19739 * 19740 * Copyright © 2012-2015, JEDLSoft 19741 * 19742 * Licensed under the Apache License, Version 2.0 (the "License"); 19743 * you may not use this file except in compliance with the License. 19744 * You may obtain a copy of the License at 19745 * 19746 * http://www.apache.org/licenses/LICENSE-2.0 19747 * 19748 * Unless required by applicable law or agreed to in writing, software 19749 * distributed under the License is distributed on an "AS IS" BASIS, 19750 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19751 * 19752 * See the License for the specific language governing permissions and 19753 * limitations under the License. 19754 */ 19755 19756 // !depends IString.js isAlpha.js isDigit.js 19757 19758 19759 /** 19760 * Return whether or not the first character is alphabetic or numeric.<p> 19761 * 19762 * @static 19763 * @param {string|IString|number} ch character or code point to examine 19764 * @return {boolean} true if the first character is alphabetic or numeric 19765 */ 19766 var isAlnum = function (ch) { 19767 var num; 19768 switch (typeof(ch)) { 19769 case 'number': 19770 num = ch; 19771 break; 19772 case 'string': 19773 num = IString.toCodePoint(ch, 0); 19774 break; 19775 case 'undefined': 19776 return false; 19777 default: 19778 num = ch._toCodePoint(0); 19779 break; 19780 } 19781 return isAlpha(num) || isDigit(num); 19782 }; 19783 19784 /** 19785 * @protected 19786 * @param {boolean} sync 19787 * @param {Object|undefined} loadParams 19788 * @param {function(*)|undefined} onLoad 19789 */ 19790 isAlnum._init = function (sync, loadParams, onLoad) { 19791 isAlpha._init(sync, loadParams, function () { 19792 isDigit._init(sync, loadParams, onLoad); 19793 }); 19794 }; 19795 19796 19797 19798 /*< isAscii.js */ 19799 /* 19800 * isAscii.js - Character type is ASCII 19801 * 19802 * Copyright © 2012-2015, JEDLSoft 19803 * 19804 * Licensed under the Apache License, Version 2.0 (the "License"); 19805 * you may not use this file except in compliance with the License. 19806 * You may obtain a copy of the License at 19807 * 19808 * http://www.apache.org/licenses/LICENSE-2.0 19809 * 19810 * Unless required by applicable law or agreed to in writing, software 19811 * distributed under the License is distributed on an "AS IS" BASIS, 19812 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19813 * 19814 * See the License for the specific language governing permissions and 19815 * limitations under the License. 19816 */ 19817 19818 // !depends CType.js IString.js ilib.js 19819 19820 // !data ctype 19821 19822 19823 /** 19824 * Return whether or not the first character is in the ASCII range.<p> 19825 * 19826 * @static 19827 * @param {string|IString|number} ch character or code point to examine 19828 * @return {boolean} true if the first character is in the ASCII range. 19829 */ 19830 var isAscii = function (ch) { 19831 var num; 19832 switch (typeof(ch)) { 19833 case 'number': 19834 num = ch; 19835 break; 19836 case 'string': 19837 num = IString.toCodePoint(ch, 0); 19838 break; 19839 case 'undefined': 19840 return false; 19841 default: 19842 num = ch._toCodePoint(0); 19843 break; 19844 } 19845 return ilib.data.ctype ? CType._inRange(num, 'ascii', ilib.data.ctype) : (num <= 0x7F); 19846 }; 19847 19848 /** 19849 * @protected 19850 * @param {boolean} sync 19851 * @param {Object|undefined} loadParams 19852 * @param {function(*)|undefined} onLoad 19853 */ 19854 isAscii._init = function (sync, loadParams, onLoad) { 19855 CType._init(sync, loadParams, onLoad); 19856 }; 19857 19858 19859 /*< isBlank.js */ 19860 /* 19861 * isBlank.js - Character type is blank 19862 * 19863 * Copyright © 2012-2015, JEDLSoft 19864 * 19865 * Licensed under the Apache License, Version 2.0 (the "License"); 19866 * you may not use this file except in compliance with the License. 19867 * You may obtain a copy of the License at 19868 * 19869 * http://www.apache.org/licenses/LICENSE-2.0 19870 * 19871 * Unless required by applicable law or agreed to in writing, software 19872 * distributed under the License is distributed on an "AS IS" BASIS, 19873 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19874 * 19875 * See the License for the specific language governing permissions and 19876 * limitations under the License. 19877 */ 19878 19879 // !depends CType.js IString.js ilib.js 19880 19881 // !data ctype 19882 19883 19884 /** 19885 * Return whether or not the first character is a blank character.<p> 19886 * 19887 * @static 19888 * ie. a space or a tab. 19889 * @param {string|IString|number} ch character or code point to examine 19890 * @return {boolean} true if the first character is a blank character. 19891 */ 19892 var isBlank = function (ch) { 19893 var num; 19894 switch (typeof(ch)) { 19895 case 'number': 19896 num = ch; 19897 break; 19898 case 'string': 19899 num = IString.toCodePoint(ch, 0); 19900 break; 19901 case 'undefined': 19902 return false; 19903 default: 19904 num = ch._toCodePoint(0); 19905 break; 19906 } 19907 return ilib.data.ctype ? CType._inRange(num, 'blank', ilib.data.ctype) : (ch === ' ' || ch === '\t'); 19908 }; 19909 19910 /** 19911 * @protected 19912 * @param {boolean} sync 19913 * @param {Object|undefined} loadParams 19914 * @param {function(*)|undefined} onLoad 19915 */ 19916 isBlank._init = function (sync, loadParams, onLoad) { 19917 CType._init(sync, loadParams, onLoad); 19918 }; 19919 19920 19921 /*< isCntrl.js */ 19922 /* 19923 * isCntrl.js - Character type is control character 19924 * 19925 * Copyright © 2012-2015, JEDLSoft 19926 * 19927 * Licensed under the Apache License, Version 2.0 (the "License"); 19928 * you may not use this file except in compliance with the License. 19929 * You may obtain a copy of the License at 19930 * 19931 * http://www.apache.org/licenses/LICENSE-2.0 19932 * 19933 * Unless required by applicable law or agreed to in writing, software 19934 * distributed under the License is distributed on an "AS IS" BASIS, 19935 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19936 * 19937 * See the License for the specific language governing permissions and 19938 * limitations under the License. 19939 */ 19940 19941 // !depends CType.js IString.js ilib.js 19942 19943 // !data ctype_c 19944 19945 19946 /** 19947 * Return whether or not the first character is a control character.<p> 19948 * 19949 * @static 19950 * @param {string|IString|number} ch character or code point to examine 19951 * @return {boolean} true if the first character is a control character. 19952 */ 19953 var isCntrl = function (ch) { 19954 var num; 19955 switch (typeof(ch)) { 19956 case 'number': 19957 num = ch; 19958 break; 19959 case 'string': 19960 num = IString.toCodePoint(ch, 0); 19961 break; 19962 case 'undefined': 19963 return false; 19964 default: 19965 num = ch._toCodePoint(0); 19966 break; 19967 } 19968 return CType._inRange(num, 'Cc', ilib.data.ctype_c); 19969 }; 19970 19971 /** 19972 * @protected 19973 * @param {boolean} sync 19974 * @param {Object|undefined} loadParams 19975 * @param {function(*)|undefined} onLoad 19976 */ 19977 isCntrl._init = function (sync, loadParams, onLoad) { 19978 CType._load("ctype_c", sync, loadParams, onLoad); 19979 }; 19980 19981 19982 /*< isGraph.js */ 19983 /* 19984 * isGraph.js - Character type is graph char 19985 * 19986 * Copyright © 2012-2015, JEDLSoft 19987 * 19988 * Licensed under the Apache License, Version 2.0 (the "License"); 19989 * you may not use this file except in compliance with the License. 19990 * You may obtain a copy of the License at 19991 * 19992 * http://www.apache.org/licenses/LICENSE-2.0 19993 * 19994 * Unless required by applicable law or agreed to in writing, software 19995 * distributed under the License is distributed on an "AS IS" BASIS, 19996 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19997 * 19998 * See the License for the specific language governing permissions and 19999 * limitations under the License. 20000 */ 20001 20002 // !depends IString.js isSpace.js isCntrl.js 20003 20004 20005 /** 20006 * Return whether or not the first character is any printable character 20007 * other than space.<p> 20008 * 20009 * @static 20010 * @param {string|IString|number} ch character or code point to examine 20011 * @return {boolean} true if the first character is any printable character 20012 * other than space. 20013 */ 20014 var isGraph = function (ch) { 20015 var num; 20016 switch (typeof(ch)) { 20017 case 'number': 20018 num = ch; 20019 break; 20020 case 'string': 20021 num = IString.toCodePoint(ch, 0); 20022 break; 20023 case 'undefined': 20024 return false; 20025 default: 20026 num = ch._toCodePoint(0); 20027 break; 20028 } 20029 return typeof(ch) !== 'undefined' && ch.length > 0 && !isSpace(num) && !isCntrl(num); 20030 }; 20031 20032 /** 20033 * @protected 20034 * @param {boolean} sync 20035 * @param {Object|undefined} loadParams 20036 * @param {function(*)|undefined} onLoad 20037 */ 20038 isGraph._init = function (sync, loadParams, onLoad) { 20039 isSpace._init(sync, loadParams, function () { 20040 isCntrl._init(sync, loadParams, onLoad); 20041 }); 20042 }; 20043 20044 20045 20046 /*< isIdeo.js */ 20047 /* 20048 * CType.js - Character type definitions 20049 * 20050 * Copyright © 2012-2015, JEDLSoft 20051 * 20052 * Licensed under the Apache License, Version 2.0 (the "License"); 20053 * you may not use this file except in compliance with the License. 20054 * You may obtain a copy of the License at 20055 * 20056 * http://www.apache.org/licenses/LICENSE-2.0 20057 * 20058 * Unless required by applicable law or agreed to in writing, software 20059 * distributed under the License is distributed on an "AS IS" BASIS, 20060 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20061 * 20062 * See the License for the specific language governing permissions and 20063 * limitations under the License. 20064 */ 20065 20066 // !depends CType.js IString.js 20067 20068 // !data ctype 20069 20070 20071 /** 20072 * Return whether or not the first character is an ideographic character.<p> 20073 * 20074 * @static 20075 * @param {string|IString|number} ch character or code point to examine 20076 * @return {boolean} true if the first character is an ideographic character. 20077 */ 20078 var isIdeo = function (ch) { 20079 var num; 20080 switch (typeof(ch)) { 20081 case 'number': 20082 num = ch; 20083 break; 20084 case 'string': 20085 num = IString.toCodePoint(ch, 0); 20086 break; 20087 case 'undefined': 20088 return false; 20089 default: 20090 num = ch._toCodePoint(0); 20091 break; 20092 } 20093 20094 return CType._inRange(num, 'cjk', ilib.data.ctype) || 20095 CType._inRange(num, 'cjkradicals', ilib.data.ctype) || 20096 CType._inRange(num, 'enclosedcjk', ilib.data.ctype) || 20097 CType._inRange(num, 'cjkpunct', ilib.data.ctype) || 20098 CType._inRange(num, 'cjkcompatibility', ilib.data.ctype); 20099 }; 20100 20101 /** 20102 * @protected 20103 * @param {boolean} sync 20104 * @param {Object|undefined} loadParams 20105 * @param {function(*)|undefined} onLoad 20106 */ 20107 isIdeo._init = function (sync, loadParams, onLoad) { 20108 CType._init(sync, loadParams, onLoad); 20109 }; 20110 20111 20112 /*< isLower.js */ 20113 /* 20114 * isLower.js - Character type is lower case letter 20115 * 20116 * Copyright © 2012-2015, JEDLSoft 20117 * 20118 * Licensed under the Apache License, Version 2.0 (the "License"); 20119 * you may not use this file except in compliance with the License. 20120 * You may obtain a copy of the License at 20121 * 20122 * http://www.apache.org/licenses/LICENSE-2.0 20123 * 20124 * Unless required by applicable law or agreed to in writing, software 20125 * distributed under the License is distributed on an "AS IS" BASIS, 20126 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20127 * 20128 * See the License for the specific language governing permissions and 20129 * limitations under the License. 20130 */ 20131 20132 // !depends CType.js IString.js 20133 20134 // !data ctype_l 20135 20136 20137 20138 /** 20139 * Return whether or not the first character is lower-case. For alphabetic 20140 * characters in scripts that do not make a distinction between upper- and 20141 * lower-case, this function always returns true.<p> 20142 * 20143 * @static 20144 * @param {string|IString|number} ch character or code point to examine 20145 * @return {boolean} true if the first character is lower-case. 20146 */ 20147 var isLower = function (ch) { 20148 var num; 20149 switch (typeof(ch)) { 20150 case 'number': 20151 num = ch; 20152 break; 20153 case 'string': 20154 num = IString.toCodePoint(ch, 0); 20155 break; 20156 case 'undefined': 20157 return false; 20158 default: 20159 num = ch._toCodePoint(0); 20160 break; 20161 } 20162 20163 return ilib.data.ctype_l ? CType._inRange(num, 'Ll', ilib.data.ctype_l) : (num >= 0x61 && num <= 0x7A); 20164 }; 20165 20166 /** 20167 * @protected 20168 * @param {boolean} sync 20169 * @param {Object|undefined} loadParams 20170 * @param {function(*)|undefined} onLoad 20171 */ 20172 isLower._init = function (sync, loadParams, onLoad) { 20173 CType._load("ctype_l", sync, loadParams, onLoad); 20174 }; 20175 20176 20177 /*< isPrint.js */ 20178 /* 20179 * isPrint.js - Character type is printable char 20180 * 20181 * Copyright © 2012-2015, JEDLSoft 20182 * 20183 * Licensed under the Apache License, Version 2.0 (the "License"); 20184 * you may not use this file except in compliance with the License. 20185 * You may obtain a copy of the License at 20186 * 20187 * http://www.apache.org/licenses/LICENSE-2.0 20188 * 20189 * Unless required by applicable law or agreed to in writing, software 20190 * distributed under the License is distributed on an "AS IS" BASIS, 20191 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20192 * 20193 * See the License for the specific language governing permissions and 20194 * limitations under the License. 20195 */ 20196 20197 // !depends isCntrl.js 20198 20199 20200 /** 20201 * Return whether or not the first character is any printable character, 20202 * including space.<p> 20203 * 20204 * @static 20205 * @param {string|IString|number} ch character or code point to examine 20206 * @return {boolean} true if the first character is printable. 20207 */ 20208 var isPrint = function (ch) { 20209 return typeof(ch) !== 'undefined' && ch.length > 0 && !isCntrl(ch); 20210 }; 20211 20212 /** 20213 * @protected 20214 * @param {boolean} sync 20215 * @param {Object|undefined} loadParams 20216 * @param {function(*)|undefined} onLoad 20217 */ 20218 isPrint._init = function (sync, loadParams, onLoad) { 20219 isCntrl._init(sync, loadParams, onLoad); 20220 }; 20221 20222 20223 /*< isPunct.js */ 20224 /* 20225 * isPunct.js - Character type is punctuation 20226 * 20227 * Copyright © 2012-2015, JEDLSoft 20228 * 20229 * Licensed under the Apache License, Version 2.0 (the "License"); 20230 * you may not use this file except in compliance with the License. 20231 * You may obtain a copy of the License at 20232 * 20233 * http://www.apache.org/licenses/LICENSE-2.0 20234 * 20235 * Unless required by applicable law or agreed to in writing, software 20236 * distributed under the License is distributed on an "AS IS" BASIS, 20237 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20238 * 20239 * See the License for the specific language governing permissions and 20240 * limitations under the License. 20241 */ 20242 20243 // !depends CType.js IString.js 20244 20245 // !data ctype_p 20246 20247 20248 20249 /** 20250 * Return whether or not the first character is punctuation.<p> 20251 * 20252 * @static 20253 * @param {string|IString|number} ch character or code point to examine 20254 * @return {boolean} true if the first character is punctuation. 20255 */ 20256 var isPunct = function (ch) { 20257 var num; 20258 switch (typeof(ch)) { 20259 case 'number': 20260 num = ch; 20261 break; 20262 case 'string': 20263 num = IString.toCodePoint(ch, 0); 20264 break; 20265 case 'undefined': 20266 return false; 20267 default: 20268 num = ch._toCodePoint(0); 20269 break; 20270 } 20271 20272 return ilib.data.ctype_p ? 20273 (CType._inRange(num, 'Pd', ilib.data.ctype_p) || 20274 CType._inRange(num, 'Ps', ilib.data.ctype_p) || 20275 CType._inRange(num, 'Pe', ilib.data.ctype_p) || 20276 CType._inRange(num, 'Pc', ilib.data.ctype_p) || 20277 CType._inRange(num, 'Po', ilib.data.ctype_p) || 20278 CType._inRange(num, 'Pi', ilib.data.ctype_p) || 20279 CType._inRange(num, 'Pf', ilib.data.ctype_p)) : 20280 ((num >= 0x21 && num <= 0x2F) || 20281 (num >= 0x3A && num <= 0x40) || 20282 (num >= 0x5B && num <= 0x60) || 20283 (num >= 0x7B && num <= 0x7E)); 20284 }; 20285 20286 /** 20287 * @protected 20288 * @param {boolean} sync 20289 * @param {Object|undefined} loadParams 20290 * @param {function(*)|undefined} onLoad 20291 */ 20292 isPunct._init = function (sync, loadParams, onLoad) { 20293 CType._load("ctype_p", sync, loadParams, onLoad); 20294 }; 20295 20296 20297 20298 /*< isUpper.js */ 20299 /* 20300 * isUpper.js - Character type is upper-case letter 20301 * 20302 * Copyright © 2012-2015, JEDLSoft 20303 * 20304 * Licensed under the Apache License, Version 2.0 (the "License"); 20305 * you may not use this file except in compliance with the License. 20306 * You may obtain a copy of the License at 20307 * 20308 * http://www.apache.org/licenses/LICENSE-2.0 20309 * 20310 * Unless required by applicable law or agreed to in writing, software 20311 * distributed under the License is distributed on an "AS IS" BASIS, 20312 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20313 * 20314 * See the License for the specific language governing permissions and 20315 * limitations under the License. 20316 */ 20317 20318 // !depends CType.js IString.js 20319 20320 // !data ctype_l 20321 20322 20323 20324 /** 20325 * Return whether or not the first character is upper-case. For alphabetic 20326 * characters in scripts that do not make a distinction between upper- and 20327 * lower-case, this function always returns true.<p> 20328 * 20329 * @static 20330 * @param {string|IString|number} ch character or code point to examine 20331 * @return {boolean} true if the first character is upper-case. 20332 */ 20333 var isUpper = function (ch) { 20334 var num; 20335 switch (typeof(ch)) { 20336 case 'number': 20337 num = ch; 20338 break; 20339 case 'string': 20340 num = IString.toCodePoint(ch, 0); 20341 break; 20342 case 'undefined': 20343 return false; 20344 default: 20345 num = ch._toCodePoint(0); 20346 break; 20347 } 20348 20349 return ilib.data.ctype_l ? CType._inRange(num, 'Lu', ilib.data.ctype_l) : (num >= 0x41 && num <= 0x5A); 20350 }; 20351 20352 /** 20353 * @protected 20354 * @param {boolean} sync 20355 * @param {Object|undefined} loadParams 20356 * @param {function(*)|undefined} onLoad 20357 */ 20358 isUpper._init = function (sync, loadParams, onLoad) { 20359 CType._load("ctype_l", sync, loadParams, onLoad); 20360 }; 20361 20362 20363 /*< isXdigit.js */ 20364 /* 20365 * isXdigit.js - Character type is hex digit 20366 * 20367 * Copyright © 2012-2015, JEDLSoft 20368 * 20369 * Licensed under the Apache License, Version 2.0 (the "License"); 20370 * you may not use this file except in compliance with the License. 20371 * You may obtain a copy of the License at 20372 * 20373 * http://www.apache.org/licenses/LICENSE-2.0 20374 * 20375 * Unless required by applicable law or agreed to in writing, software 20376 * distributed under the License is distributed on an "AS IS" BASIS, 20377 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20378 * 20379 * See the License for the specific language governing permissions and 20380 * limitations under the License. 20381 */ 20382 20383 // !depends CType.js IString.js 20384 20385 // !data ctype 20386 20387 20388 20389 /** 20390 * Return whether or not the first character is a hexadecimal digit written 20391 * in the Latin script. (0-9 or A-F)<p> 20392 * 20393 * @static 20394 * @param {string|IString|number} ch character or code point to examine 20395 * @return {boolean} true if the first character is a hexadecimal digit written 20396 * in the Latin script. 20397 */ 20398 var isXdigit = function (ch) { 20399 var num; 20400 switch (typeof(ch)) { 20401 case 'number': 20402 num = ch; 20403 break; 20404 case 'string': 20405 num = IString.toCodePoint(ch, 0); 20406 break; 20407 case 'undefined': 20408 return false; 20409 default: 20410 num = ch._toCodePoint(0); 20411 break; 20412 } 20413 20414 return ilib.data.ctype ? CType._inRange(num, 'xdigit', ilib.data.ctype) : 20415 ((num >= 0x30 && num <= 0x39) || (num >= 0x41 && num <= 0x46) || (num >= 0x61 && num <= 0x66)); 20416 }; 20417 20418 /** 20419 * @protected 20420 * @param {boolean} sync 20421 * @param {Object|undefined} loadParams 20422 * @param {function(*)|undefined} onLoad 20423 */ 20424 isXdigit._init = function (sync, loadParams, onLoad) { 20425 CType._init(sync, loadParams, onLoad); 20426 }; 20427 20428 20429 /*< isScript.js */ 20430 /* 20431 * isScript.js - Character type is script 20432 * 20433 * Copyright © 2012-2015, JEDLSoft 20434 * 20435 * Licensed under the Apache License, Version 2.0 (the "License"); 20436 * you may not use this file except in compliance with the License. 20437 * You may obtain a copy of the License at 20438 * 20439 * http://www.apache.org/licenses/LICENSE-2.0 20440 * 20441 * Unless required by applicable law or agreed to in writing, software 20442 * distributed under the License is distributed on an "AS IS" BASIS, 20443 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20444 * 20445 * See the License for the specific language governing permissions and 20446 * limitations under the License. 20447 */ 20448 20449 // !depends CType.js IString.js 20450 20451 // !data scriptToRange 20452 20453 20454 20455 /** 20456 * Return whether or not the first character in the given string is 20457 * in the given script. The script is given as the 4-letter ISO 20458 * 15924 script code.<p> 20459 * 20460 * @static 20461 * @param {string|IString|number} ch character or code point to examine 20462 * @param {string} script the 4-letter ISO 15924 to query against 20463 * @return {boolean} true if the first character is in the given script, and 20464 * false otherwise 20465 */ 20466 var isScript = function (ch, script) { 20467 var num; 20468 switch (typeof(ch)) { 20469 case 'number': 20470 num = ch; 20471 break; 20472 case 'string': 20473 num = IString.toCodePoint(ch, 0); 20474 break; 20475 case 'undefined': 20476 return false; 20477 default: 20478 num = ch._toCodePoint(0); 20479 break; 20480 } 20481 20482 return CType._inRange(num, script, ilib.data.scriptToRange); 20483 }; 20484 20485 /** 20486 * @protected 20487 * @param {boolean} sync 20488 * @param {Object|undefined} loadParams 20489 * @param {function(*)|undefined} onLoad 20490 */ 20491 isScript._init = function (sync, loadParams, onLoad) { 20492 CType._load("scriptToRange", sync, loadParams, onLoad); 20493 }; 20494 20495 20496 /*< Name.js */ 20497 /* 20498 * Name.js - Person name parser 20499 * 20500 * Copyright © 2013-2015, 2018, JEDLSoft 20501 * 20502 * Licensed under the Apache License, Version 2.0 (the "License"); 20503 * you may not use this file except in compliance with the License. 20504 * You may obtain a copy of the License at 20505 * 20506 * http://www.apache.org/licenses/LICENSE-2.0 20507 * 20508 * Unless required by applicable law or agreed to in writing, software 20509 * distributed under the License is distributed on an "AS IS" BASIS, 20510 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 20511 * 20512 * See the License for the specific language governing permissions and 20513 * limitations under the License. 20514 */ 20515 20516 /* !depends 20517 ilib.js 20518 Locale.js 20519 Utils.js 20520 isAlpha.js 20521 isIdeo.js 20522 isPunct.js 20523 isSpace.js 20524 JSUtils.js 20525 IString.js 20526 */ 20527 20528 // !data name 20529 20530 // notes: 20531 // icelandic given names: http://en.wiktionary.org/wiki/Appendix:Icelandic_given_names 20532 // danish approved given names: http://www.familiestyrelsen.dk/samliv/navne/ 20533 // http://www.mentalfloss.com/blogs/archives/59277 20534 // other countries with first name restrictions: Norway, China, New Zealand, Japan, Sweden, Germany, Hungary 20535 20536 20537 20538 /** 20539 * @class 20540 * A class to parse names of people. Different locales have different conventions when it 20541 * comes to naming people.<p> 20542 * 20543 * The options can contain any of the following properties: 20544 * 20545 * <ul> 20546 * <li><i>locale</i> - use the rules and conventions of the given locale in order to parse 20547 * the name 20548 * <li><i>style</i> - explicitly use the named style to parse the name. Valid values so 20549 * far are "western" and "asian". If this property is not specified, then the style will 20550 * be gleaned from the name itself. This class will count the total number of Latin or Asian 20551 * characters. If the majority of the characters are in one style, that style will be 20552 * used to parse the whole name. 20553 * <li><i>order</i> - explicitly use the given order for names. In some locales, such 20554 * as Russian, names may be written equally validly as "givenName familyName" or "familyName 20555 * givenName". This option tells the parser which order to prefer, and overrides the 20556 * default order for the locale. Valid values are "gf" (given-family) or "fg" (family-given). 20557 * <li><i>useSpaces</i> - explicitly specifies whether to use spaces or not between the given name , middle name 20558 * and family name. 20559 * <li><i>compoundFamilyName</i> - for Asian and some other types of names, search for compound 20560 * family names. If this parameter is not specified, the default is to use the setting that is 20561 * most common for the locale. If it is specified, the locale default is 20562 * overridden with this flag. 20563 * <li>onLoad - a callback function to call when the name info is fully 20564 * loaded and the name has been parsed. When the onLoad option is given, the name object 20565 * will attempt to load any missing locale data using the ilib loader callback. 20566 * When the constructor is done (even if the data is already preassembled), the 20567 * onLoad function is called with the current instance as a parameter, so this 20568 * callback can be used with preassembled or dynamic loading or a mix of the two. 20569 * 20570 * <li>sync - tell whether to load any missing locale data synchronously or 20571 * asynchronously. If this option is given as "false", then the "onLoad" 20572 * callback must be given, as the instance returned from this constructor will 20573 * not be usable for a while. 20574 * 20575 * <li><i>loadParams</i> - an object containing parameters to pass to the 20576 * loader callback function when locale data is missing. The parameters are not 20577 * interpretted or modified in any way. They are simply passed along. The object 20578 * may contain any property/value pairs as long as the calling code is in 20579 * agreement with the loader callback function as to what those parameters mean. 20580 * </ul> 20581 * 20582 * Additionally, a name instance can be constructed by giving the explicit 20583 * already-parsed fields or by using another name instance as the parameter. (That is, 20584 * it becomes a copy constructor.) The name fields can be any of the following: 20585 * 20586 * <ul> 20587 * <li><i>prefix</i> - the prefix prepended to the name 20588 * <li><i>givenName</i> - the person's given or "first" name 20589 * <li><i>middleName</i> - one or more middle names, separated by spaces even if the 20590 * language doesn't use usually use spaces. The spaces are merely separators. 20591 * <li><i>familyName</i> - one or more family or "last" names, separated by spaces 20592 * even if the language doesn't use usually use spaces. The spaces are merely separators. 20593 * <li><i>suffix</i> - the suffix appended to the name 20594 * <li><i>honorific</i> - the honorific title of the name. This could be formatted 20595 * as a prefix or a suffix, depending on the customs of the particular locale. You 20596 * should only give either an honorific or a prefix/suffix, but not both. 20597 * </ul> 20598 * 20599 * When the parser has completed its parsing, it fills in the same fields as listed 20600 * above.<p> 20601 * 20602 * For names that include auxilliary words, such as the family name "van der Heijden", all 20603 * of the auxilliary words ("van der") will be included in the field.<p> 20604 * 20605 * For names in some Spanish locales, it is assumed that the family name is doubled. That is, 20606 * a person may have a paternal family name followed by a maternal family name. All 20607 * family names will be listed in the familyName field as normal, separated by spaces. 20608 * When formatting the short version of such names, only the paternal family name is used. 20609 * 20610 * @constructor 20611 * @param {string|Name|Object=} name the name to parse 20612 * @param {Object=} options Options governing the construction of this name instance 20613 */ 20614 var Name = function (name, options) { 20615 var sync = true; 20616 20617 if (!name || name.length === 0) { 20618 if (options && typeof(options.onLoad) === 'function') { 20619 options.onLoad(undefined); 20620 } 20621 return; 20622 } 20623 20624 this.loadParams = {}; 20625 20626 if (options) { 20627 if (options.locale) { 20628 this.locale = (typeof (options.locale) === 'string') ? new Locale(options.locale) : options.locale; 20629 } 20630 20631 if (options.style && (options.style === "asian" || options.style === "western")) { 20632 this.style = options.style; 20633 } 20634 20635 if (options.order && (options.order === "gmf" || options.order === "fmg" || options.order === "fgm")) { 20636 this.order = options.order; 20637 } 20638 20639 if (typeof(options.sync) === 'boolean') { 20640 sync = options.sync; 20641 } 20642 20643 if (typeof(options.loadParams) !== 'undefined') { 20644 this.loadParams = options.loadParams; 20645 } 20646 20647 if (typeof(options.compoundFamilyName) !== 'undefined') { 20648 this.singleFamilyName = !options.compoundFamilyName; 20649 } 20650 } 20651 20652 this.locale = this.locale || new Locale(); 20653 20654 isAlpha._init(sync, this.loadParams, ilib.bind(this, function() { 20655 isIdeo._init(sync, this.loadParams, ilib.bind(this, function() { 20656 isPunct._init(sync, this.loadParams, ilib.bind(this, function() { 20657 isSpace._init(sync, this.loadParams, ilib.bind(this, function() { 20658 Utils.loadData({ 20659 object: "Name", 20660 locale: this.locale, 20661 name: "name.json", 20662 sync: sync, 20663 loadParams: this.loadParams, 20664 callback: ilib.bind(this, function (info) { 20665 if (!info) { 20666 info = Name.defaultInfo[this.style || "western"]; 20667 var spec = this.locale.getSpec().replace(/-/g, "_"); 20668 ilib.data.cache.Name[spec] = info; 20669 } 20670 if (typeof (name) === 'object') { 20671 // copy constructor 20672 /** 20673 * The prefixes for this name 20674 * @type {string|Array.<string>} 20675 */ 20676 this.prefix = name.prefix; 20677 /** 20678 * The given (personal) name in this name. 20679 * @type {string|Array.<string>} 20680 */ 20681 this.givenName = name.givenName; 20682 /** 20683 * The middle names used in this name. If there are multiple middle names, they all 20684 * appear in this field separated by spaces. 20685 * @type {string|Array.<string>} 20686 */ 20687 this.middleName = name.middleName; 20688 /** 20689 * The family names in this name. If there are multiple family names, they all 20690 * appear in this field separated by spaces. 20691 * @type {string|Array.<string>} 20692 */ 20693 this.familyName = name.familyName; 20694 /** 20695 * The suffixes for this name. If there are multiple suffixes, they all 20696 * appear in this field separated by spaces. 20697 * @type {string|Array.<string>} 20698 */ 20699 this.suffix = name.suffix; 20700 /** 20701 * The honorific title for this name. This honorific will be used as the prefix 20702 * or suffix as dictated by the locale 20703 * @type {string|Array.<string>} 20704 */ 20705 this.honorific = name.honorific; 20706 20707 // private properties 20708 this.locale = name.locale; 20709 this.style = name.style; 20710 this.order = name.order; 20711 this.useSpaces = name.useSpaces; 20712 this.isAsianName = name.isAsianName; 20713 20714 if (options && typeof(options.onLoad) === 'function') { 20715 options.onLoad(this); 20716 } 20717 20718 return; 20719 } 20720 /** 20721 * @type {{ 20722 * nameStyle:string, 20723 * order:string, 20724 * prefixes:Array.<string>, 20725 * suffixes:Array.<string>, 20726 * auxillaries:Array.<string>, 20727 * honorifics:Array.<string>, 20728 * knownFamilyNames:Array.<string>, 20729 * noCompoundFamilyNames:boolean, 20730 * sortByHeadWord:boolean 20731 * }} */ 20732 this.info = info; 20733 this._init(name); 20734 if (options && typeof(options.onLoad) === 'function') { 20735 options.onLoad(this); 20736 } 20737 }) 20738 }); 20739 })); 20740 })); 20741 })); 20742 })); 20743 }; 20744 20745 Name.defaultInfo = { 20746 "western": ilib.data.name || { 20747 "components": { 20748 "short": "gf", 20749 "medium": "gmf", 20750 "long": "pgmf", 20751 "full": "pgmfs", 20752 "formal_short": "hf", 20753 "formal_long": "hgf" 20754 }, 20755 "format": "{prefix} {givenName} {middleName} {familyName}{suffix}", 20756 "sortByHeadWord": false, 20757 "nameStyle": "western", 20758 "conjunctions": { 20759 "and1": "and", 20760 "and2": "and", 20761 "or1": "or", 20762 "or2": "or" 20763 }, 20764 "auxillaries": { 20765 "von": 1, 20766 "von der": 1, 20767 "von den": 1, 20768 "van": 1, 20769 "van der": 1, 20770 "van de": 1, 20771 "van den": 1, 20772 "de": 1, 20773 "di": 1, 20774 "la": 1, 20775 "lo": 1, 20776 "des": 1, 20777 "le": 1, 20778 "les": 1, 20779 "du": 1, 20780 "de la": 1, 20781 "del": 1, 20782 "de los": 1, 20783 "de las": 1 20784 }, 20785 "prefixes": [ 20786 "doctor", 20787 "dr", 20788 "mr", 20789 "mrs", 20790 "ms", 20791 "mister", 20792 "madame", 20793 "madamoiselle", 20794 "miss", 20795 "monsieur", 20796 "señor", 20797 "señora", 20798 "señorita" 20799 ], 20800 "suffixes": [ 20801 ",", 20802 "junior", 20803 "jr", 20804 "senior", 20805 "sr", 20806 "i", 20807 "ii", 20808 "iii", 20809 "esq", 20810 "phd", 20811 "md" 20812 ], 20813 "patronymicName":[ ], 20814 "familyNames":[ ] 20815 }, 20816 "asian": { 20817 "components": { 20818 "short": "gf", 20819 "medium": "gmf", 20820 "long": "hgmf", 20821 "full": "hgmf", 20822 "formal_short": "hf", 20823 "formal_long": "hgf" 20824 }, 20825 "format": "{prefix}{familyName}{middleName}{givenName}{suffix}", 20826 "nameStyle": "asian", 20827 "sortByHeadWord": false, 20828 "conjunctions": {}, 20829 "auxillaries": {}, 20830 "prefixes": [], 20831 "suffixes": [], 20832 "patronymicName":[], 20833 "familyNames":[] 20834 } 20835 }; 20836 20837 /** 20838 * Return true if the given character is in the range of the Han, Hangul, or kana 20839 * scripts. 20840 * @static 20841 * @protected 20842 */ 20843 Name._isAsianChar = function(c) { 20844 return isIdeo(c) || 20845 CType.withinRange(c, "hangul") || 20846 CType.withinRange(c, "hiragana") || 20847 CType.withinRange(c, "katakana"); 20848 }; 20849 20850 20851 /** 20852 * @static 20853 * @protected 20854 */ 20855 Name._isAsianName = function (name, language) { 20856 // the idea is to count the number of asian chars and the number 20857 // of latin chars. If one is greater than the other, choose 20858 // that style. 20859 var asian = 0, 20860 latin = 0, 20861 i; 20862 20863 if (name && name.length > 0) { 20864 for (i = 0; i < name.length; i++) { 20865 var c = name.charAt(i); 20866 20867 if (Name._isAsianChar(c)) { 20868 if (language =="ko" || language =="ja" || language =="zh") { 20869 return true; 20870 } 20871 asian++; 20872 } else if (isAlpha(c)) { 20873 if (!language =="ko" || !language =="ja" || !language =="zh") { 20874 return false; 20875 } 20876 latin++; 20877 } 20878 } 20879 20880 return latin < asian; 20881 } 20882 20883 return false; 20884 }; 20885 20886 /** 20887 * Return true if any Latin letters are found in the string. Return 20888 * false if all the characters are non-Latin. 20889 * @static 20890 * @protected 20891 */ 20892 Name._isEuroName = function (name, language) { 20893 var c, 20894 n = new IString(name), 20895 it = n.charIterator(); 20896 20897 while (it.hasNext()) { 20898 c = it.next(); 20899 20900 if (!Name._isAsianChar(c) && !isPunct(c) && !isSpace(c)) { 20901 return true; 20902 } else if (Name._isAsianChar(c) && (language =="ko" || language =="ja" || language =="zh")) { 20903 return false; 20904 } 20905 } 20906 return false; 20907 }; 20908 20909 Name.prototype = { 20910 /** 20911 * @protected 20912 */ 20913 _init: function (name) { 20914 var parts, prefixArray, prefix, prefixLower, 20915 suffixArray, suffix, suffixLower, 20916 i, info, hpSuffix; 20917 var currentLanguage = this.locale.getLanguage(); 20918 20919 if (name) { 20920 // for DFISH-12905, pick off the part that the LDAP server automatically adds to our names in HP emails 20921 i = name.search(/\s*[,\/\(\[\{<]/); 20922 if (i !== -1) { 20923 hpSuffix = name.substring(i); 20924 hpSuffix = hpSuffix.replace(/\s+/g, ' '); // compress multiple whitespaces 20925 suffixArray = hpSuffix.split(" "); 20926 var conjunctionIndex = this._findLastConjunction(suffixArray); 20927 if (conjunctionIndex > -1) { 20928 // it's got conjunctions in it, so this is not really a suffix 20929 hpSuffix = undefined; 20930 } else { 20931 name = name.substring(0, i); 20932 } 20933 } 20934 20935 this.isAsianName = Name._isAsianName(name, currentLanguage); 20936 if (this.info.nameStyle === "asian") { 20937 info = this.isAsianName ? this.info : Name.defaultInfo.western; 20938 } else { 20939 info = this.isAsianName ? Name.defaultInfo.asian : this.info; 20940 } 20941 20942 if (this.isAsianName) { 20943 // all-asian names 20944 if (this.useSpaces === false) { 20945 name = name.replace(/\s+/g, ''); // eliminate all whitespaces 20946 } 20947 parts = name.trim().split(''); 20948 } 20949 //} 20950 else { 20951 name = name.replace(/, /g, ' , '); 20952 name = name.replace(/\s+/g, ' '); // compress multiple whitespaces 20953 parts = name.trim().split(' '); 20954 } 20955 20956 // check for prefixes 20957 if (parts.length > 1) { 20958 for (i = parts.length; i > 0; i--) { 20959 prefixArray = parts.slice(0, i); 20960 prefix = prefixArray.join(this.isAsianName ? '' : ' '); 20961 prefixLower = prefix.toLowerCase(); 20962 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 20963 if (ilib.isArray(this.info.prefixes) && 20964 (JSUtils.indexOf(this.info.prefixes, prefixLower) > -1 || this._isConjunction(prefixLower))) { 20965 if (this.prefix) { 20966 if (!this.isAsianName) { 20967 this.prefix += ' '; 20968 } 20969 this.prefix += prefix; 20970 } else { 20971 this.prefix = prefix; 20972 } 20973 parts = parts.slice(i); 20974 i = parts.length; 20975 } 20976 } 20977 } 20978 // check for suffixes 20979 if (parts.length > 1) { 20980 for (i = parts.length; i > 0; i--) { 20981 suffixArray = parts.slice(-i); 20982 suffix = suffixArray.join(this.isAsianName ? '' : ' '); 20983 suffixLower = suffix.toLowerCase(); 20984 suffixLower = suffixLower.replace(/[\.]/g, ''); // ignore periods 20985 if (ilib.isArray(this.info.suffixes) && JSUtils.indexOf(this.info.suffixes, suffixLower) > -1) { 20986 if (this.suffix) { 20987 if (!this.isAsianName && !isPunct(this.suffix.charAt(0))) { 20988 this.suffix = ' ' + this.suffix; 20989 } 20990 this.suffix = suffix + this.suffix; 20991 } else { 20992 this.suffix = suffix; 20993 } 20994 parts = parts.slice(0, parts.length - i); 20995 i = parts.length; 20996 } 20997 } 20998 } 20999 21000 if (hpSuffix) { 21001 this.suffix = (this.suffix && this.suffix + hpSuffix) || hpSuffix; 21002 } 21003 21004 // adjoin auxillary words to their headwords 21005 if (parts.length > 1 && !this.isAsianName) { 21006 parts = this._joinAuxillaries(parts, this.isAsianName); 21007 } 21008 21009 if (this.isAsianName) { 21010 this._parseAsianName(parts, currentLanguage); 21011 } else { 21012 this._parseWesternName(parts); 21013 } 21014 21015 this._joinNameArrays(); 21016 } 21017 }, 21018 21019 /** 21020 * @return {number} 21021 * 21022 _findSequence: function(parts, hash, isAsian) { 21023 var sequence, sequenceLower, sequenceArray, aux = [], i, ret = {}; 21024 21025 if (parts.length > 0 && hash) { 21026 //console.info("_findSequence: finding sequences"); 21027 for (var start = 0; start < parts.length-1; start++) { 21028 for ( i = parts.length; i > start; i-- ) { 21029 sequenceArray = parts.slice(start, i); 21030 sequence = sequenceArray.join(isAsian ? '' : ' '); 21031 sequenceLower = sequence.toLowerCase(); 21032 sequenceLower = sequenceLower.replace(/[,\.]/g, ''); // ignore commas and periods 21033 21034 //console.info("_findSequence: checking sequence: '" + sequenceLower + "'"); 21035 21036 if ( sequenceLower in hash ) { 21037 ret.match = sequenceArray; 21038 ret.start = start; 21039 ret.end = i; 21040 return ret; 21041 //console.info("_findSequence: Found sequence '" + sequence + "' New parts list is " + JSON.stringify(parts)); 21042 } 21043 } 21044 } 21045 } 21046 21047 return undefined; 21048 }, 21049 */ 21050 21051 /** 21052 * @protected 21053 * @param {Array} parts 21054 * @param {Array} names 21055 * @param {boolean} isAsian 21056 * @param {boolean=} noCompoundPrefix 21057 */ 21058 _findPrefix: function (parts, names, isAsian, noCompoundPrefix) { 21059 var i, prefix, prefixLower, prefixArray, aux = []; 21060 21061 if (parts.length > 0 && names) { 21062 for (i = parts.length; i > 0; i--) { 21063 prefixArray = parts.slice(0, i); 21064 prefix = prefixArray.join(isAsian ? '' : ' '); 21065 prefixLower = prefix.toLowerCase(); 21066 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 21067 21068 if (prefixLower in names) { 21069 aux = aux.concat(isAsian ? prefix : prefixArray); 21070 if (noCompoundPrefix) { 21071 // don't need to parse further. Just return it as is. 21072 return aux; 21073 } 21074 parts = parts.slice(i); 21075 i = parts.length + 1; 21076 } 21077 } 21078 } 21079 21080 return aux; 21081 }, 21082 21083 /** 21084 * @protected 21085 */ 21086 _findSuffix: function (parts, names, isAsian) { 21087 var i, j, seq = ""; 21088 21089 for (i = 0; i < names.length; i++) { 21090 if (parts.length >= names[i].length) { 21091 j = 0; 21092 while (j < names[i].length && parts[parts.length - j] === names[i][names[i].length - j]) { 21093 j++; 21094 } 21095 if (j >= names[i].length) { 21096 seq = parts.slice(parts.length - j).join(isAsian ? "" : " ") + (isAsian ? "" : " ") + seq; 21097 parts = parts.slice(0, parts.length - j); 21098 i = -1; // restart the search 21099 } 21100 } 21101 } 21102 21103 this.suffix = seq; 21104 return parts; 21105 }, 21106 21107 /** 21108 * @protected 21109 * Tell whether or not the given word is a conjunction in this language. 21110 * @param {string} word the word to test 21111 * @return {boolean} true if the word is a conjunction 21112 */ 21113 _isConjunction: function _isConjunction(word) { 21114 return (this.info.conjunctions.and1 === word || 21115 this.info.conjunctions.and2 === word || 21116 this.info.conjunctions.or1 === word || 21117 this.info.conjunctions.or2 === word || 21118 ("&" === word) || 21119 ("+" === word)); 21120 }, 21121 21122 /** 21123 * Find the last instance of 'and' in the name 21124 * @protected 21125 * @param {Array.<string>} parts 21126 * @return {number} 21127 */ 21128 _findLastConjunction: function _findLastConjunction(parts) { 21129 var conjunctionIndex = -1, 21130 index, part; 21131 21132 for (index = 0; index < parts.length; index++) { 21133 part = parts[index]; 21134 if (typeof (part) === 'string') { 21135 part = part.toLowerCase(); 21136 // also recognize English 21137 if ("and" === part || "or" === part || "&" === part || "+" === part) { 21138 conjunctionIndex = index; 21139 } 21140 if (this._isConjunction(part)) { 21141 conjunctionIndex = index; 21142 } 21143 } 21144 } 21145 return conjunctionIndex; 21146 }, 21147 21148 /** 21149 * @protected 21150 * @param {Array.<string>} parts the current array of name parts 21151 * @param {boolean} isAsian true if the name is being parsed as an Asian name 21152 * @return {Array.<string>} the remaining parts after the prefixes have been removed 21153 */ 21154 _extractPrefixes: function (parts, isAsian) { 21155 var i = this._findPrefix(parts, this.info.prefixes, isAsian); 21156 if (i > 0) { 21157 this.prefix = parts.slice(0, i).join(isAsian ? "" : " "); 21158 return parts.slice(i); 21159 } 21160 // prefixes not found, so just return the array unmodified 21161 return parts; 21162 }, 21163 21164 /** 21165 * @protected 21166 * @param {Array.<string>} parts the current array of name parts 21167 * @param {boolean} isAsian true if the name is being parsed as an Asian name 21168 * @return {Array.<string>} the remaining parts after the suffices have been removed 21169 */ 21170 _extractSuffixes: function (parts, isAsian) { 21171 var i = this._findSuffix(parts, this.info.suffixes, isAsian); 21172 if (i > 0) { 21173 this.suffix = parts.slice(i).join(isAsian ? "" : " "); 21174 return parts.slice(0, i); 21175 } 21176 // suffices not found, so just return the array unmodified 21177 return parts; 21178 }, 21179 21180 /** 21181 * Adjoin auxillary words to their head words. 21182 * @protected 21183 * @param {Array.<string>} parts the current array of name parts 21184 * @param {boolean} isAsian true if the name is being parsed as an Asian name 21185 * @return {Array.<string>} the parts after the auxillary words have been plucked onto their head word 21186 */ 21187 _joinAuxillaries: function (parts, isAsian) { 21188 var start, i, prefixArray, prefix, prefixLower; 21189 21190 if (this.info.auxillaries && (parts.length > 2 || this.prefix)) { 21191 for (start = 0; start < parts.length - 1; start++) { 21192 for (i = parts.length; i > start; i--) { 21193 prefixArray = parts.slice(start, i); 21194 prefix = prefixArray.join(' '); 21195 prefixLower = prefix.toLowerCase(); 21196 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 21197 21198 if (prefixLower in this.info.auxillaries) { 21199 parts.splice(start, i + 1 - start, prefixArray.concat(parts[i])); 21200 i = start; 21201 } 21202 } 21203 } 21204 } 21205 21206 return parts; 21207 }, 21208 21209 /** 21210 * Recursively join an array or string into a long string. 21211 * @protected 21212 */ 21213 _joinArrayOrString: function _joinArrayOrString(part) { 21214 var i; 21215 if (typeof (part) === 'object') { 21216 for (i = 0; i < part.length; i++) { 21217 part[i] = this._joinArrayOrString(part[i]); 21218 } 21219 var ret = ""; 21220 part.forEach(function (segment) { 21221 if (ret.length > 0 && !isPunct(segment.charAt(0))) { 21222 ret += ' '; 21223 } 21224 ret += segment; 21225 }); 21226 21227 return ret; 21228 } 21229 21230 return part; 21231 }, 21232 21233 /** 21234 * @protected 21235 */ 21236 _joinNameArrays: function _joinNameArrays() { 21237 var prop; 21238 for (prop in this) { 21239 21240 if (this[prop] !== undefined && typeof (this[prop]) === 'object' && ilib.isArray(this[prop])) { 21241 21242 this[prop] = this._joinArrayOrString(this[prop]); 21243 } 21244 } 21245 }, 21246 21247 /** 21248 * @protected 21249 */ 21250 _parseAsianName: function (parts, language) { 21251 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, typeof(this.singleFamilyName) !== 'undefined' ? this.singleFamilyName : this.info.noCompoundFamilyNames); 21252 var tempFullName = parts.join(''); 21253 21254 if (familyNameArray && familyNameArray.length > 0) { 21255 this.familyName = familyNameArray.join(''); 21256 this.givenName = parts.slice(this.familyName.length).join(''); 21257 21258 //Overide parsing rules if spaces are found in korean 21259 if (language === "ko" && tempFullName.search(/\s*[/\s]/) > -1 && !this.suffix) { 21260 this._parseKoreanName(tempFullName); 21261 } 21262 } else if (this.locale.getLanguage() === "ja") { 21263 this._parseJapaneseName(parts); 21264 } else if (this.suffix || this.prefix) { 21265 this.familyName = parts.join(''); 21266 } else { 21267 this.givenName = parts.join(''); 21268 } 21269 }, 21270 21271 /** 21272 * @protected 21273 */ 21274 _parseKoreanName: function (name) { 21275 var tempName = name; 21276 21277 var spaceSplit = tempName.split(" "); 21278 var spceCount = spaceSplit.length; 21279 var fistSpaceIndex = tempName.indexOf(" "); 21280 var lastSpaceIndex = tempName.lastIndexOf(" "); 21281 21282 if (spceCount === 2) { 21283 this.familyName = spaceSplit[0]; 21284 this.givenName = tempName.slice(fistSpaceIndex, tempName.length); 21285 } else { 21286 this.familyName = spaceSplit[0]; 21287 this.middleName = tempName.slice(fistSpaceIndex, lastSpaceIndex); 21288 this.givenName = tempName.slice(lastSpaceIndex, tempName.length); 21289 } 21290 21291 }, 21292 21293 /** 21294 * @protected 21295 */ 21296 _parseJapaneseName: function (parts) { 21297 if (this.suffix && this.suffix.length > 1 && this.info.honorifics.indexOf(this.suffix)>-1) { 21298 if (parts.length === 1) { 21299 if (CType.withinRange(parts[0], "cjk")) { 21300 this.familyName = parts[0]; 21301 } else { 21302 this.givenName = parts[0]; 21303 } 21304 return; 21305 } else if (parts.length === 2) { 21306 this.familyName = parts.slice(0,parts.length).join("") 21307 return; 21308 } 21309 } 21310 if (parts.length > 1) { 21311 var fn = ""; 21312 for (var i = 0; i < parts.length; i++) { 21313 if (CType.withinRange(parts[i], "cjk")) { 21314 fn += parts[i]; 21315 } else if (fn.length > 1 && CType.withinRange(parts[i], "hiragana")) { 21316 this.familyName = fn; 21317 this.givenName = parts.slice(i,parts.length).join(""); 21318 return; 21319 } else { 21320 break; 21321 } 21322 } 21323 } 21324 if (parts.length === 1) { 21325 this.familyName = parts[0]; 21326 } else if (parts.length === 2) { 21327 this.familyName = parts[0]; 21328 this.givenName = parts[1]; 21329 } else if (parts.length === 3) { 21330 this.familyName = parts[0]; 21331 this.givenName = parts.slice(1,parts.length).join(""); 21332 } else if (parts.length > 3) { 21333 this.familyName = parts.slice(0,2).join("") 21334 this.givenName = parts.slice(2,parts.length).join(""); 21335 } 21336 }, 21337 21338 /** 21339 * @protected 21340 */ 21341 _parseSpanishName: function (parts) { 21342 var conjunctionIndex; 21343 21344 if (parts.length === 1) { 21345 if (this.prefix || typeof (parts[0]) === 'object') { 21346 this.familyName = parts[0]; 21347 } else { 21348 this.givenName = parts[0]; 21349 } 21350 } else if (parts.length === 2) { 21351 // we do G F 21352 this.givenName = parts[0]; 21353 this.familyName = parts[1]; 21354 } else if (parts.length === 3) { 21355 conjunctionIndex = this._findLastConjunction(parts); 21356 // if there's an 'and' in the middle spot, put everything in the first name 21357 if (conjunctionIndex === 1) { 21358 this.givenName = parts; 21359 } else { 21360 // else, do G F F 21361 this.givenName = parts[0]; 21362 this.familyName = parts.slice(1); 21363 } 21364 } else if (parts.length > 3) { 21365 //there are at least 4 parts to this name 21366 21367 conjunctionIndex = this._findLastConjunction(parts); 21368 ////console.log("@@@@@@@@@@@@@@@@"+conjunctionIndex) 21369 if (conjunctionIndex > 0) { 21370 // if there's a conjunction that's not the first token, put everything up to and 21371 // including the token after it into the first name, the last 2 tokens into 21372 // the family name (if they exist) and everything else in to the middle name 21373 // 0 1 2 3 4 5 21374 // G A G 21375 // G A G F 21376 // G G A G 21377 // G A G F F 21378 // G G A G F 21379 // G G G A G 21380 // G A G M F F 21381 // G G A G F F 21382 // G G G A G F 21383 // G G G G A G 21384 this.givenName = parts.splice(0, conjunctionIndex + 2); 21385 if (parts.length > 1) { 21386 this.familyName = parts.splice(parts.length - 2, 2); 21387 if (parts.length > 0) { 21388 this.middleName = parts; 21389 } 21390 } else if (parts.length === 1) { 21391 this.familyName = parts[0]; 21392 } 21393 } else { 21394 this.givenName = parts.splice(0, 1); 21395 this.familyName = parts.splice(parts.length - 2, 2); 21396 this.middleName = parts; 21397 } 21398 } 21399 }, 21400 21401 /** 21402 * @protected 21403 */ 21404 _parseIndonesianName: function (parts) { 21405 var conjunctionIndex; 21406 21407 if (parts.length === 1) { 21408 //if (this.prefix || typeof(parts[0]) === 'object') { 21409 //this.familyName = parts[0]; 21410 //} else { 21411 this.givenName = parts[0]; 21412 //} 21413 //} else if (parts.length === 2) { 21414 // we do G F 21415 //this.givenName = parts[0]; 21416 //this.familyName = parts[1]; 21417 } else if (parts.length >= 2) { 21418 //there are at least 3 parts to this name 21419 21420 conjunctionIndex = this._findLastConjunction(parts); 21421 if (conjunctionIndex > 0) { 21422 // if there's a conjunction that's not the first token, put everything up to and 21423 // including the token after it into the first name, the last 2 tokens into 21424 // the family name (if they exist) and everything else in to the middle name 21425 // 0 1 2 3 4 5 21426 // G A G 21427 // G A G F 21428 // G G A G 21429 // G A G F F 21430 // G G A G F 21431 // G G G A G 21432 // G A G M F F 21433 // G G A G F F 21434 // G G G A G F 21435 // G G G G A G 21436 this.givenName = parts.splice(0, conjunctionIndex + 2); 21437 if (parts.length > 1) { 21438 //this.familyName = parts.splice(parts.length-2, 2); 21439 //if ( parts.length > 0 ) { 21440 this.middleName = parts; 21441 } 21442 //} else if (parts.length === 1) { 21443 // this.familyName = parts[0]; 21444 //} 21445 } else { 21446 this.givenName = parts.splice(0, 1); 21447 //this.familyName = parts.splice(parts.length-2, 2); 21448 this.middleName = parts; 21449 } 21450 } 21451 }, 21452 21453 /** 21454 * @protected 21455 */ 21456 _parseGenericWesternName: function (parts) { 21457 /* Western names are parsed as follows, and rules are applied in this 21458 * order: 21459 * 21460 * G 21461 * G F 21462 * G M F 21463 * G M M F 21464 * P F 21465 * P G F 21466 */ 21467 var conjunctionIndex; 21468 21469 if (parts.length === 1) { 21470 if (this.prefix || typeof (parts[0]) === 'object') { 21471 // already has a prefix, so assume it goes with the family name like "Dr. Roberts" or 21472 // it is a name with auxillaries, which is almost always a family name 21473 this.familyName = parts[0]; 21474 } else { 21475 this.givenName = parts[0]; 21476 } 21477 } else if (parts.length === 2) { 21478 // we do G F 21479 if (this.info.order == 'fgm') { 21480 this.givenName = parts[1]; 21481 this.familyName = parts[0]; 21482 } else if (this.info.order == "gmf" || typeof (this.info.order) == 'undefined') { 21483 this.givenName = parts[0]; 21484 this.familyName = parts[1]; 21485 } 21486 } else if (parts.length >= 3) { 21487 //find the first instance of 'and' in the name 21488 conjunctionIndex = this._findLastConjunction(parts); 21489 21490 if (conjunctionIndex > 0) { 21491 // if there's a conjunction that's not the first token, put everything up to and 21492 // including the token after it into the first name, the last token into 21493 // the family name (if it exists) and everything else in to the middle name 21494 // 0 1 2 3 4 5 21495 // G A G M M F 21496 // G G A G M F 21497 // G G G A G F 21498 // G G G G A G 21499 //if(this.order == "gmf") { 21500 this.givenName = parts.slice(0, conjunctionIndex + 2); 21501 21502 if (conjunctionIndex + 1 < parts.length - 1) { 21503 this.familyName = parts.splice(parts.length - 1, 1); 21504 ////console.log(this.familyName); 21505 if (conjunctionIndex + 2 < parts.length - 1) { 21506 this.middleName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 21507 } 21508 } else if (this.info.order == "fgm") { 21509 this.familyName = parts.slice(0, conjunctionIndex + 2); 21510 if (conjunctionIndex + 1 < parts.length - 1) { 21511 this.middleName = parts.splice(parts.length - 1, 1); 21512 if (conjunctionIndex + 2 < parts.length - 1) { 21513 this.givenName = parts.slice(conjunctionIndex + 2, parts.length - conjunctionIndex - 3); 21514 } 21515 } 21516 } 21517 } else if (this.info.order === "fgm") { 21518 this.givenName = parts[1]; 21519 this.middleName = parts.slice(2); 21520 this.familyName = parts[0]; 21521 } else { 21522 this.givenName = parts[0]; 21523 this.middleName = parts.slice(1, parts.length - 1); 21524 this.familyName = parts[parts.length - 1]; 21525 } 21526 } 21527 }, 21528 21529 /** 21530 * parse patrinomic name from the russian names 21531 * @protected 21532 * @param {Array.<string>} parts the current array of name parts 21533 * @return number index of the part which contains patronymic name 21534 */ 21535 _findPatronymicName: function(parts) { 21536 var index, part; 21537 for (index = 0; index < parts.length; index++) { 21538 part = parts[index]; 21539 if (typeof (part) === 'string') { 21540 part = part.toLowerCase(); 21541 21542 var subLength = this.info.patronymicName.length; 21543 while(subLength--) { 21544 if(part.indexOf(this.info.patronymicName[subLength])!== -1 ) 21545 return index; 21546 } 21547 } 21548 } 21549 return -1; 21550 }, 21551 21552 /** 21553 * find if the given part is patronymic name 21554 * 21555 * @protected 21556 * @param {string} part string from name parts @ 21557 * @return number index of the part which contains familyName 21558 */ 21559 _isPatronymicName: function(part) { 21560 var pName; 21561 if ( typeof (part) === 'string') { 21562 pName = part.toLowerCase(); 21563 21564 var subLength = this.info.patronymicName.length; 21565 while (subLength--) { 21566 if (pName.indexOf(this.info.patronymicName[subLength]) !== -1) 21567 return true; 21568 } 21569 } 21570 return false; 21571 }, 21572 21573 /** 21574 * find family name from the russian name 21575 * 21576 * @protected 21577 * @param {Array.<string>} parts the current array of name parts 21578 * @return boolean true if patronymic, false otherwise 21579 */ 21580 _findFamilyName: function(parts) { 21581 var index, part, substring; 21582 for (index = 0; index < parts.length; index++) { 21583 part = parts[index]; 21584 21585 if ( typeof (part) === 'string') { 21586 part = part.toLowerCase(); 21587 var length = part.length - 1; 21588 21589 if (this.info.familyName.indexOf(part) !== -1) { 21590 return index; 21591 } else if (part[length] === 'в' || part[length] === 'н' || 21592 part[length] === 'й') { 21593 substring = part.slice(0, -1); 21594 if (this.info.familyName.indexOf(substring) !== -1) { 21595 return index; 21596 } 21597 } else if ((part[length - 1] === 'в' && part[length] === 'а') || 21598 (part[length - 1] === 'н' && part[length] === 'а') || 21599 (part[length - 1] === 'а' && part[length] === 'я')) { 21600 substring = part.slice(0, -2); 21601 if (this.info.familyName.indexOf(substring) !== -1) { 21602 return index; 21603 } 21604 } 21605 } 21606 } 21607 return -1; 21608 }, 21609 21610 /** 21611 * parse russian name 21612 * 21613 * @protected 21614 * @param {Array.<string>} parts the current array of name parts 21615 * @return 21616 */ 21617 _parseRussianName: function(parts) { 21618 var conjunctionIndex, familyIndex = -1; 21619 21620 if (parts.length === 1) { 21621 if (this.prefix || typeof (parts[0]) === 'object') { 21622 // already has a prefix, so assume it goes with the family name 21623 // like "Dr. Roberts" or 21624 // it is a name with auxillaries, which is almost always a 21625 // family name 21626 this.familyName = parts[0]; 21627 } else { 21628 this.givenName = parts[0]; 21629 } 21630 } else if (parts.length === 2) { 21631 // we do G F 21632 if (this.info.order === 'fgm') { 21633 this.givenName = parts[1]; 21634 this.familyName = parts[0]; 21635 } else if (this.info.order === "gmf") { 21636 this.givenName = parts[0]; 21637 this.familyName = parts[1]; 21638 } else if ( typeof (this.info.order) === 'undefined') { 21639 if (this._isPatronymicName(parts[1]) === true) { 21640 this.middleName = parts[1]; 21641 this.givenName = parts[0]; 21642 } else if ((familyIndex = this._findFamilyName(parts)) !== -1) { 21643 if (familyIndex === 1) { 21644 this.givenName = parts[0]; 21645 this.familyName = parts[1]; 21646 } else { 21647 this.familyName = parts[0]; 21648 this.givenName = parts[1]; 21649 } 21650 21651 } else { 21652 this.givenName = parts[0]; 21653 this.familyName = parts[1]; 21654 } 21655 21656 } 21657 } else if (parts.length >= 3) { 21658 // find the first instance of 'and' in the name 21659 conjunctionIndex = this._findLastConjunction(parts); 21660 var patronymicNameIndex = this._findPatronymicName(parts); 21661 if (conjunctionIndex > 0) { 21662 // if there's a conjunction that's not the first token, put 21663 // everything up to and 21664 // including the token after it into the first name, the last 21665 // token into 21666 // the family name (if it exists) and everything else in to the 21667 // middle name 21668 // 0 1 2 3 4 5 21669 // G A G M M F 21670 // G G A G M F 21671 // G G G A G F 21672 // G G G G A G 21673 // if(this.order == "gmf") { 21674 this.givenName = parts.slice(0, conjunctionIndex + 2); 21675 21676 if (conjunctionIndex + 1 < parts.length - 1) { 21677 this.familyName = parts.splice(parts.length - 1, 1); 21678 // //console.log(this.familyName); 21679 if (conjunctionIndex + 2 < parts.length - 1) { 21680 this.middleName = parts.slice(conjunctionIndex + 2, 21681 parts.length - conjunctionIndex - 3); 21682 } 21683 } else if (this.order == "fgm") { 21684 this.familyName = parts.slice(0, conjunctionIndex + 2); 21685 if (conjunctionIndex + 1 < parts.length - 1) { 21686 this.middleName = parts.splice(parts.length - 1, 1); 21687 if (conjunctionIndex + 2 < parts.length - 1) { 21688 this.givenName = parts.slice(conjunctionIndex + 2, 21689 parts.length - conjunctionIndex - 3); 21690 } 21691 } 21692 } 21693 } else if (patronymicNameIndex !== -1) { 21694 this.middleName = parts[patronymicNameIndex]; 21695 21696 if (patronymicNameIndex === (parts.length - 1)) { 21697 this.familyName = parts[0]; 21698 this.givenName = parts.slice(1, patronymicNameIndex); 21699 } else { 21700 this.givenName = parts.slice(0, patronymicNameIndex); 21701 21702 this.familyName = parts[parts.length - 1]; 21703 } 21704 } else { 21705 this.givenName = parts[0]; 21706 21707 this.middleName = parts.slice(1, parts.length - 1); 21708 21709 this.familyName = parts[parts.length - 1]; 21710 } 21711 } 21712 }, 21713 21714 21715 /** 21716 * @protected 21717 */ 21718 _parseWesternName: function (parts) { 21719 21720 if (this.locale.getLanguage() === "es" || this.locale.getLanguage() === "pt") { 21721 // in spain and mexico and portugal, we parse names differently than in the rest of the world 21722 // because of the double family names 21723 this._parseSpanishName(parts); 21724 } else if (this.locale.getLanguage() === "ru") { 21725 /* 21726 * In Russian, names can be given equally validly as given-family 21727 * or family-given. Use the value of the "order" property of the 21728 * constructor options to give the default when the order is ambiguous. 21729 */ 21730 this._parseRussianName(parts); 21731 } else if (this.locale.getLanguage() === "id") { 21732 // in indonesia, we parse names differently than in the rest of the world 21733 // because names don't have family names usually. 21734 this._parseIndonesianName(parts); 21735 } else { 21736 this._parseGenericWesternName(parts); 21737 } 21738 }, 21739 21740 /** 21741 * When sorting names with auxiliary words (like "van der" or "de los"), determine 21742 * which is the "head word" and return a string that can be easily sorted by head 21743 * word. In English, names are always sorted by initial characters. In places like 21744 * the Netherlands or Germany, family names are sorted by the head word of a list 21745 * of names rather than the first element of that name. 21746 * @return {string|undefined} a string containing the family name[s] to be used for sorting 21747 * in the current locale, or undefined if there is no family name in this object 21748 */ 21749 getSortFamilyName: function () { 21750 var name, 21751 auxillaries, 21752 auxString, 21753 parts, 21754 i; 21755 21756 // no name to sort by 21757 if (!this.familyName) { 21758 return undefined; 21759 } 21760 21761 // first break the name into parts 21762 if (this.info) { 21763 if (this.info.sortByHeadWord) { 21764 if (typeof (this.familyName) === 'string') { 21765 name = this.familyName.replace(/\s+/g, ' '); // compress multiple whitespaces 21766 parts = name.trim().split(' '); 21767 } else { 21768 // already split 21769 parts = this.familyName; 21770 } 21771 21772 auxillaries = this._findPrefix(parts, this.info.auxillaries, false); 21773 if (auxillaries && auxillaries.length > 0) { 21774 if (typeof (this.familyName) === 'string') { 21775 auxString = auxillaries.join(' '); 21776 name = this.familyName.substring(auxString.length + 1) + ', ' + auxString; 21777 } else { 21778 name = parts.slice(auxillaries.length).join(' ') + 21779 ', ' + 21780 parts.slice(0, auxillaries.length).join(' '); 21781 } 21782 } 21783 } else if (this.info.knownFamilyNames && this.familyName) { 21784 parts = this.familyName.split(''); 21785 var familyNameArray = this._findPrefix(parts, this.info.knownFamilyNames, true, this.info.noCompoundFamilyNames); 21786 name = ""; 21787 for (i = 0; i < familyNameArray.length; i++) { 21788 name += (this.info.knownFamilyNames[familyNameArray[i]] || ""); 21789 } 21790 } 21791 } 21792 21793 return name || this.familyName; 21794 }, 21795 21796 getHeadFamilyName: function () {}, 21797 21798 /** 21799 * @protected 21800 * Return a shallow copy of the current instance. 21801 */ 21802 clone: function () { 21803 return new Name(this); 21804 } 21805 }; 21806 21807 21808 /*< NameFmt.js */ 21809 /* 21810 * NameFmt.js - Format person names for display 21811 * 21812 * Copyright © 2013-2015, JEDLSoft 21813 * 21814 * Licensed under the Apache License, Version 2.0 (the "License"); 21815 * you may not use this file except in compliance with the License. 21816 * You may obtain a copy of the License at 21817 * 21818 * http://www.apache.org/licenses/LICENSE-2.0 21819 * 21820 * Unless required by applicable law or agreed to in writing, software 21821 * distributed under the License is distributed on an "AS IS" BASIS, 21822 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 21823 * 21824 * See the License for the specific language governing permissions and 21825 * limitations under the License. 21826 */ 21827 21828 /* !depends 21829 ilib.js 21830 Locale.js 21831 IString.js 21832 Name.js 21833 isPunct.js 21834 Utils.js 21835 */ 21836 21837 // !data name 21838 21839 21840 21841 21842 /** 21843 * @class 21844 * Creates a formatter that can format person name instances (Name) for display to 21845 * a user. The options may contain the following properties: 21846 * 21847 * <ul> 21848 * <li><i>locale</i> - Use the conventions of the given locale to construct the name format. 21849 * <li><i>style</i> - Format the name with the given style. The value of this property 21850 * should be one of the following strings: 21851 * <ul> 21852 * <li><i>short</i> - Format a short name with just the given and family names. eg. "John Smith" 21853 * <li><i>medium</i> - Format a medium-length name with the given, middle, and family names. 21854 * eg. "John James Smith" 21855 * <li><i>long</i> - Format a long name with all names available in the given name object, including 21856 * prefixes. eg. "Mr. John James Smith" 21857 * <li><i>full</i> - Format a long name with all names available in the given name object, including 21858 * prefixes and suffixes. eg. "Mr. John James Smith, Jr." 21859 * <li><i>formal_short</i> - Format a name with the honorific or prefix/suffix and the family 21860 * name. eg. "Mr. Smith" 21861 * <li><i>formal_long</i> - Format a name with the honorific or prefix/suffix and the 21862 * given and family name. eg. "Mr. John Smith" 21863 * </ul> 21864 * <li><i>components</i> - Format the name with the given components in the correct 21865 * order for those components. Components are encoded as a string of letters representing 21866 * the desired components: 21867 * <ul> 21868 * <li><i>p</i> - prefixes 21869 * <li><i>g</i> - given name 21870 * <li><i>m</i> - middle names 21871 * <li><i>f</i> - family name 21872 * <li><i>s</i> - suffixes 21873 * <li><i>h</i> - honorifics (selects the prefix or suffix as required by the locale) 21874 * </ul> 21875 * <p> 21876 * 21877 * For example, the string "pf" would mean to only format any prefixes and family names 21878 * together and leave out all the other parts of the name.<p> 21879 * 21880 * The components can be listed in any order in the string. The <i>components</i> option 21881 * overrides the <i>style</i> option if both are specified. 21882 * 21883 * <li>onLoad - a callback function to call when the locale info object is fully 21884 * loaded. When the onLoad option is given, the localeinfo object will attempt to 21885 * load any missing locale data using the ilib loader callback. 21886 * When the constructor is done (even if the data is already preassembled), the 21887 * onLoad function is called with the current instance as a parameter, so this 21888 * callback can be used with preassembled or dynamic loading or a mix of the two. 21889 * 21890 * <li>sync - tell whether to load any missing locale data synchronously or 21891 * asynchronously. If this option is given as "false", then the "onLoad" 21892 * callback must be given, as the instance returned from this constructor will 21893 * not be usable for a while. 21894 * 21895 * <li><i>loadParams</i> - an object containing parameters to pass to the 21896 * loader callback function when locale data is missing. The parameters are not 21897 * interpretted or modified in any way. They are simply passed along. The object 21898 * may contain any property/value pairs as long as the calling code is in 21899 * agreement with the loader callback function as to what those parameters mean. 21900 * </ul> 21901 * 21902 * Formatting names is a locale-dependent function, as the order of the components 21903 * depends on the locale. The following explains some of the details:<p> 21904 * 21905 * <ul> 21906 * <li>In Western countries, the given name comes first, followed by a space, followed 21907 * by the family name. In Asian countries, the family name comes first, followed immediately 21908 * by the given name with no space. But, that format is only used with Asian names written 21909 * in ideographic characters. In Asian countries, especially ones where both an Asian and 21910 * a Western language are used (Hong Kong, Singapore, etc.), the convention is often to 21911 * follow the language of the name. That is, Asian names are written in Asian style, and 21912 * Western names are written in Western style. This class follows that convention as 21913 * well. 21914 * <li>In other Asian countries, Asian names 21915 * written in Latin script are written with Asian ordering. eg. "Xu Ping-an" instead 21916 * of the more Western order "Ping-an Xu", as the order is thought to go with the style 21917 * that is appropriate for the name rather than the style for the language being written. 21918 * <li>In some Spanish speaking countries, people often take both their maternal and 21919 * paternal last names as their own family name. When formatting a short or medium style 21920 * of that family name, only the paternal name is used. In the long style, all the names 21921 * are used. eg. "Juan Julio Raul Lopez Ortiz" took the name "Lopez" from his father and 21922 * the name "Ortiz" from his mother. His family name would be "Lopez Ortiz". The formatted 21923 * short style of his name would be simply "Juan Lopez" which only uses his paternal 21924 * family name of "Lopez". 21925 * <li>In many Western languages, it is common to use auxillary words in family names. For 21926 * example, the family name of "Ludwig von Beethoven" in German is "von Beethoven", not 21927 * "Beethoven". This class ensures that the family name is formatted correctly with 21928 * all auxillary words. 21929 * </ul> 21930 * 21931 * 21932 * @constructor 21933 * @param {Object} options A set of options that govern how the formatter will behave 21934 */ 21935 var NameFmt = function(options) { 21936 var sync = true; 21937 21938 this.style = "short"; 21939 this.loadParams = {}; 21940 21941 if (options) { 21942 if (options.locale) { 21943 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 21944 } 21945 21946 if (options.style) { 21947 this.style = options.style; 21948 } 21949 21950 if (options.components) { 21951 this.components = options.components; 21952 } 21953 21954 if (typeof(options.sync) !== 'undefined') { 21955 sync = !!options.sync; 21956 } 21957 21958 if (typeof(options.loadParams) !== 'undefined') { 21959 this.loadParams = options.loadParams; 21960 } 21961 } 21962 21963 // set up defaults in case we need them 21964 this.defaultEuroTemplate = new IString("{prefix} {givenName} {middleName} {familyName}{suffix}"); 21965 this.defaultAsianTemplate = new IString("{prefix}{familyName}{givenName}{middleName}{suffix}"); 21966 this.useFirstFamilyName = false; 21967 21968 switch (this.style) { 21969 default: 21970 case "s": 21971 case "short": 21972 this.style = "short"; 21973 break; 21974 case "m": 21975 case "medium": 21976 this.style = "medium"; 21977 break; 21978 case "l": 21979 case "long": 21980 this.style = "long"; 21981 break; 21982 case "f": 21983 case "full": 21984 this.style = "full"; 21985 break; 21986 case "fs": 21987 case "formal_short": 21988 this.style = "formal_short"; 21989 break; 21990 case "fl": 21991 case "formal_long": 21992 this.style = "formal_long"; 21993 break; 21994 } 21995 21996 this.locale = this.locale || new Locale(); 21997 21998 isPunct._init(sync, this.loadParams, ilib.bind(this, function() { 21999 Utils.loadData({ 22000 object: "Name", 22001 locale: this.locale, 22002 name: "name.json", 22003 sync: sync, 22004 loadParams: this.loadParams, 22005 callback: ilib.bind(this, function (info) { 22006 if (!info) { 22007 info = Name.defaultInfo; 22008 var spec = this.locale.getSpec().replace(/-/g, "_"); 22009 ilib.data.cache.Name[spec] = info; 22010 } 22011 this.info = info; 22012 this._init(); 22013 if (options && typeof(options.onLoad) === 'function') { 22014 options.onLoad(this); 22015 } 22016 }) 22017 }); 22018 })); 22019 }; 22020 22021 NameFmt.prototype = { 22022 /** 22023 * @protected 22024 */ 22025 _init: function() { 22026 var arr; 22027 this.comps = {}; 22028 22029 if (this.components) { 22030 var valids = {"p":1,"g":1,"m":1,"f":1,"s":1,"h":1}; 22031 arr = this.components.split(""); 22032 this.comps = {}; 22033 for (var i = 0; i < arr.length; i++) { 22034 if (valids[arr[i].toLowerCase()]) { 22035 this.comps[arr[i].toLowerCase()] = true; 22036 } 22037 } 22038 } else { 22039 var comps = this.info.components[this.style]; 22040 if (typeof(comps) === "string") { 22041 comps.split("").forEach(ilib.bind(this, function(c) { 22042 this.comps[c] = true; 22043 })); 22044 } else { 22045 this.comps = comps; 22046 } 22047 } 22048 22049 this.template = new IString(this.info.format); 22050 22051 if (this.locale.language === "es" && (this.style !== "long" && this.style !== "full")) { 22052 this.useFirstFamilyName = true; // in spanish, they have 2 family names, the maternal and paternal 22053 } 22054 22055 this.isAsianLocale = (this.info.nameStyle === "asian"); 22056 }, 22057 22058 /** 22059 * adjoin auxillary words to their head words 22060 * @protected 22061 */ 22062 _adjoinAuxillaries: function (parts, namePrefix) { 22063 var start, i, prefixArray, prefix, prefixLower; 22064 22065 //console.info("_adjoinAuxillaries: finding and adjoining aux words in " + parts.join(' ')); 22066 22067 if ( this.info.auxillaries && (parts.length > 2 || namePrefix) ) { 22068 for ( start = 0; start < parts.length-1; start++ ) { 22069 for ( i = parts.length; i > start; i-- ) { 22070 prefixArray = parts.slice(start, i); 22071 prefix = prefixArray.join(' '); 22072 prefixLower = prefix.toLowerCase(); 22073 prefixLower = prefixLower.replace(/[,\.]/g, ''); // ignore commas and periods 22074 22075 //console.info("_adjoinAuxillaries: checking aux prefix: '" + prefixLower + "' which is " + start + " to " + i); 22076 22077 if ( prefixLower in this.info.auxillaries ) { 22078 //console.info("Found! Old parts list is " + JSON.stringify(parts)); 22079 parts.splice(start, i+1-start, prefixArray.concat(parts[i])); 22080 //console.info("_adjoinAuxillaries: Found! New parts list is " + JSON.stringify(parts)); 22081 i = start; 22082 } 22083 } 22084 } 22085 } 22086 22087 //console.info("_adjoinAuxillaries: done. Result is " + JSON.stringify(parts)); 22088 22089 return parts; 22090 }, 22091 22092 /** 22093 * Return the locale for this formatter instance. 22094 * @return {Locale} the locale instance for this formatter 22095 */ 22096 getLocale: function () { 22097 return this.locale; 22098 }, 22099 22100 /** 22101 * Return the style of names returned by this formatter 22102 * @return {string} the style of names returned by this formatter 22103 */ 22104 getStyle: function () { 22105 return this.style; 22106 }, 22107 22108 /** 22109 * Return the list of components used to format names in this formatter 22110 * @return {string} the list of components 22111 */ 22112 getComponents: function () { 22113 return this.components; 22114 }, 22115 22116 /** 22117 * Format the name for display in the current locale with the options set up 22118 * in the constructor of this formatter instance.<p> 22119 * 22120 * If the name does not contain all the parts required for the style, those parts 22121 * will be left blank.<p> 22122 * 22123 * There are two basic styles of formatting: European, and Asian. If this formatter object 22124 * is set for European style, but an Asian name is passed to the format method, then this 22125 * method will format the Asian name with a generic Asian template. Similarly, if the 22126 * formatter is set for an Asian style, and a European name is passed to the format method, 22127 * the formatter will use a generic European template.<p> 22128 * 22129 * This means it is always safe to format any name with a formatter for any locale. You should 22130 * always get something at least reasonable as output.<p> 22131 * 22132 * @param {Name|Object} name the name instance to format, or an object containing name parts to format 22133 * @return {string|undefined} the name formatted according to the style of this formatter instance 22134 */ 22135 format: function(name) { 22136 var formatted, temp, modified, isAsianName; 22137 var currentLanguage = this.locale.getLanguage(); 22138 22139 if (!name || typeof(name) !== 'object') { 22140 return undefined; 22141 } 22142 if (!(name instanceof Name)) { 22143 // if the object is not a name, implicitly convert to a name so that the code below works 22144 name = new Name(name, {locale: this.locale}); 22145 } 22146 22147 if ((typeof(name.isAsianName) === 'boolean' && !name.isAsianName) || 22148 Name._isEuroName([name.givenName, name.middleName, name.familyName].join(""), currentLanguage)) { 22149 isAsianName = false; // this is a euro name, even if the locale is asian 22150 modified = name.clone(); 22151 22152 // handle the case where there is no space if there is punctuation in the suffix like ", Phd". 22153 // Otherwise, put a space in to transform "PhD" to " PhD" 22154 /* 22155 console.log("suffix is " + modified.suffix); 22156 if ( modified.suffix ) { 22157 console.log("first char is " + modified.suffix.charAt(0)); 22158 console.log("isPunct(modified.suffix.charAt(0)) is " + isPunct(modified.suffix.charAt(0))); 22159 } 22160 */ 22161 if (modified.suffix && isPunct(modified.suffix.charAt(0)) === false) { 22162 modified.suffix = ' ' + modified.suffix; 22163 } 22164 22165 if (this.useFirstFamilyName && name.familyName) { 22166 var familyNameParts = modified.familyName.trim().split(' '); 22167 if (familyNameParts.length > 1) { 22168 familyNameParts = this._adjoinAuxillaries(familyNameParts, name.prefix); 22169 } //in spain and mexico, we parse names differently than in the rest of the world 22170 22171 modified.familyName = familyNameParts[0]; 22172 } 22173 22174 modified._joinNameArrays(); 22175 } else { 22176 isAsianName = true; 22177 modified = name; 22178 } 22179 22180 if (!this.template || isAsianName !== this.isAsianLocale) { 22181 temp = isAsianName ? this.defaultAsianTemplate : this.defaultEuroTemplate; 22182 } else { 22183 temp = this.template; 22184 } 22185 22186 // use the honorific as the prefix or the suffix as appropriate for the order of the name 22187 if (modified.honorific) { 22188 if ((this.order === 'fg' || isAsianName) && currentLanguage !== "ko") { 22189 if (!modified.suffix) { 22190 modified.suffix = modified.honorific 22191 } 22192 } else { 22193 if (!modified.prefix) { 22194 modified.prefix = modified.honorific 22195 } 22196 } 22197 } 22198 22199 var parts = { 22200 prefix: this.comps["p"] && modified.prefix || "", 22201 givenName: this.comps["g"] && modified.givenName || "", 22202 middleName: this.comps["m"] && modified.middleName || "", 22203 familyName: this.comps["f"] && modified.familyName || "", 22204 suffix: this.comps["s"] && modified.suffix || "" 22205 }; 22206 22207 formatted = temp.format(parts); 22208 return formatted.replace(/\s+/g, ' ').trim(); 22209 } 22210 }; 22211 22212 22213 22214 /*< Address.js */ 22215 /* 22216 * Address.js - Represent a mailing address 22217 * 22218 * Copyright © 2013-2015, JEDLSoft 22219 * 22220 * Licensed under the Apache License, Version 2.0 (the "License"); 22221 * you may not use this file except in compliance with the License. 22222 * You may obtain a copy of the License at 22223 * 22224 * http://www.apache.org/licenses/LICENSE-2.0 22225 * 22226 * Unless required by applicable law or agreed to in writing, software 22227 * distributed under the License is distributed on an "AS IS" BASIS, 22228 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22229 * 22230 * See the License for the specific language governing permissions and 22231 * limitations under the License. 22232 */ 22233 22234 /*globals console RegExp */ 22235 22236 /* !depends 22237 ilib.js 22238 Utils.js 22239 JSUtils.js 22240 Locale.js 22241 isIdeo.js 22242 isAscii.js 22243 isDigit.js 22244 IString.js 22245 */ 22246 22247 // !data address countries nativecountries ctrynames 22248 22249 22250 /** 22251 * @class 22252 * Create a new Address instance and parse a physical address.<p> 22253 * 22254 * This function parses a physical address written in a free-form string. 22255 * It returns an object with a number of properties from the list below 22256 * that it may have extracted from that address.<p> 22257 * 22258 * The following is a list of properties that the algorithm will return:<p> 22259 * 22260 * <ul> 22261 * <li><i>streetAddress</i>: The street address, including house numbers and all. 22262 * <li><i>locality</i>: The locality of this address (usually a city or town). 22263 * <li><i>region</i>: The region where the locality is located. In the US, this 22264 * corresponds to states. In other countries, this may be provinces, 22265 * cantons, prefectures, etc. In some smaller countries, there are no 22266 * such divisions. 22267 * <li><i>postalCode</i>: Country-specific code for expediting mail. In the US, 22268 * this is the zip code. 22269 * <li><i>country</i>: The country of the address. 22270 * <li><i>countryCode</i>: The ISO 3166 2-letter region code for the destination 22271 * country in this address. 22272 * </ul> 22273 * 22274 * The above properties will not necessarily appear in the instance. For 22275 * any individual property, if the free-form address does not contain 22276 * that property or it cannot be parsed out, the it is left out.<p> 22277 * 22278 * The options parameter may contain any of the following properties: 22279 * 22280 * <ul> 22281 * <li><i>locale</i> - locale or localeSpec to use to parse the address. If not 22282 * specified, this function will use the current ilib locale 22283 * 22284 * <li><i>onLoad</i> - a callback function to call when the address info for the 22285 * locale is fully loaded and the address has been parsed. When the onLoad 22286 * option is given, the address object 22287 * will attempt to load any missing locale data using the ilib loader callback. 22288 * When the constructor is done (even if the data is already preassembled), the 22289 * onLoad function is called with the current instance as a parameter, so this 22290 * callback can be used with preassembled or dynamic loading or a mix of the two. 22291 * 22292 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 22293 * asynchronously. If this option is given as "false", then the "onLoad" 22294 * callback must be given, as the instance returned from this constructor will 22295 * not be usable for a while. 22296 * 22297 * <li><i>loadParams</i> - an object containing parameters to pass to the 22298 * loader callback function when locale data is missing. The parameters are not 22299 * interpretted or modified in any way. They are simply passed along. The object 22300 * may contain any property/value pairs as long as the calling code is in 22301 * agreement with the loader callback function as to what those parameters mean. 22302 * </ul> 22303 * 22304 * When an address cannot be parsed properly, the entire address will be placed 22305 * into the streetAddress property.<p> 22306 * 22307 * When the freeformAddress is another Address, this will act like a copy 22308 * constructor.<p> 22309 * 22310 * 22311 * @constructor 22312 * @param {string|Address} freeformAddress free-form address to parse, or a 22313 * javascript object containing the fields 22314 * @param {Object} options options to the parser 22315 */ 22316 var Address = function (freeformAddress, options) { 22317 var address; 22318 22319 if (!freeformAddress) { 22320 return undefined; 22321 } 22322 22323 this.sync = true; 22324 this.loadParams = {}; 22325 22326 if (options) { 22327 if (options.locale) { 22328 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 22329 } 22330 22331 if (typeof(options.sync) !== 'undefined') { 22332 this.sync = !!options.sync; 22333 } 22334 22335 if (options.loadParams) { 22336 this.loadParams = options.loadParams; 22337 } 22338 } 22339 22340 this.locale = this.locale || new Locale(); 22341 // initialize from an already parsed object 22342 if (typeof(freeformAddress) === 'object') { 22343 /** 22344 * The street address, including house numbers and all. 22345 * @type {string|undefined} 22346 */ 22347 this.streetAddress = freeformAddress.streetAddress; 22348 /** 22349 * The locality of this address (usually a city or town). 22350 * @type {string|undefined} 22351 */ 22352 this.locality = freeformAddress.locality; 22353 /** 22354 * The region (province, canton, prefecture, state, etc.) where the address is located. 22355 * @type {string|undefined} 22356 */ 22357 this.region = freeformAddress.region; 22358 /** 22359 * Country-specific code for expediting mail. In the US, this is the zip code. 22360 * @type {string|undefined} 22361 */ 22362 this.postalCode = freeformAddress.postalCode; 22363 /** 22364 * Optional city-specific code for a particular post office, used to expidite 22365 * delivery. 22366 * @type {string|undefined} 22367 */ 22368 this.postOffice = freeformAddress.postOffice; 22369 /** 22370 * The country of the address. 22371 * @type {string|undefined} 22372 */ 22373 this.country = freeformAddress.country; 22374 if (freeformAddress.countryCode) { 22375 /** 22376 * The 2 or 3 letter ISO 3166 region code for the destination country in this address. 22377 * @type {string} 22378 */ 22379 this.countryCode = freeformAddress.countryCode; 22380 } 22381 if (freeformAddress.format) { 22382 /** 22383 * private 22384 * @type {string} 22385 */ 22386 this.format = freeformAddress.format; 22387 } 22388 return this; 22389 } 22390 22391 address = freeformAddress.replace(/[ \t\r]+/g, " ").trim(); 22392 address = address.replace(/[\s\n]+$/, ""); 22393 address = address.replace(/^[\s\n]+/, ""); 22394 //console.log("\n\n-------------\nAddress is '" + address + "'"); 22395 22396 this.lines = address.split(/[,,\n]/g); 22397 this.removeEmptyLines(this.lines); 22398 22399 isAscii._init(this.sync, this.loadParams, ilib.bind(this, function() { 22400 isIdeo._init(this.sync, this.loadParams, ilib.bind(this, function() { 22401 isDigit._init(this.sync, this.loadParams, ilib.bind(this, function() { 22402 if (typeof(ilib.data.nativecountries) === 'undefined') { 22403 Utils.loadData({ 22404 object: "Address", 22405 name: "nativecountries.json", // countries in their own language 22406 locale: "-", // only need to load the root file 22407 nonlocale: true, 22408 sync: this.sync, 22409 loadParams: this.loadParams, 22410 callback: ilib.bind(this, function(nativecountries) { 22411 ilib.data.nativecountries = nativecountries; 22412 this._loadCountries(options && options.onLoad); 22413 }) 22414 }); 22415 } else { 22416 this._loadCountries(options && options.onLoad); 22417 } 22418 })); 22419 })); 22420 })); 22421 }; 22422 22423 /** @protected */ 22424 Address.prototype = { 22425 /** 22426 * @private 22427 */ 22428 _loadCountries: function(onLoad) { 22429 if (typeof(ilib.data.countries) === 'undefined') { 22430 Utils.loadData({ 22431 object: "Address", 22432 name: "countries.json", // countries in English 22433 locale: "-", // only need to load the root file 22434 nonlocale: true, 22435 sync: this.sync, 22436 loadParams: this.loadParams, 22437 callback: ilib.bind(this, function(countries) { 22438 ilib.data.countries = countries; 22439 this._loadCtrynames(onLoad); 22440 }) 22441 }); 22442 } else { 22443 this._loadCtrynames(onLoad); 22444 } 22445 }, 22446 22447 /** 22448 * @private 22449 */ 22450 _loadCtrynames: function(onLoad) { 22451 Utils.loadData({ 22452 name: "ctrynames.json", 22453 object: "Address", 22454 locale: this.locale, 22455 sync: this.sync, 22456 loadParams: JSUtils.merge(this.loadParams, {returnOne: true}), 22457 callback: ilib.bind(this, function(ctrynames) { 22458 this.ctrynames = ctrynames; 22459 this._determineDest(ctrynames, onLoad); 22460 }) 22461 }); 22462 }, 22463 22464 /** 22465 * @private 22466 * @param {Object?} ctrynames 22467 */ 22468 _findDest: function (ctrynames) { 22469 var match; 22470 22471 for (var countryName in ctrynames) { 22472 if (countryName && countryName !== "generated") { 22473 // find the longest match in the current table 22474 // ctrynames contains the country names mapped to region code 22475 // for efficiency, only test for things longer than the current match 22476 if (!match || match.text.length < countryName.length) { 22477 var temp = this._findCountry(countryName); 22478 if (temp) { 22479 match = temp; 22480 this.country = match.text; 22481 this.countryCode = ctrynames[countryName]; 22482 } 22483 } 22484 } 22485 } 22486 return match; 22487 }, 22488 22489 /** 22490 * @private 22491 * @param {Object?} localizedCountries 22492 * @param {function(Address):undefined} callback 22493 */ 22494 _determineDest: function (localizedCountries, callback) { 22495 var match; 22496 22497 /* 22498 * First, find the name of the destination country, as that determines how to parse 22499 * the rest of the address. For any address, there are three possible ways 22500 * that the name of the country could be written: 22501 * 1. In the current language 22502 * 2. In its own native language 22503 * 3. In English 22504 * We'll try all three. 22505 */ 22506 var tables = []; 22507 if (localizedCountries) { 22508 tables.push(localizedCountries); 22509 } 22510 tables.push(ilib.data.nativecountries); 22511 tables.push(ilib.data.countries); 22512 22513 for (var i = 0; i < tables.length; i++) { 22514 match = this._findDest(tables[i]); 22515 22516 if (match) { 22517 this.lines[match.line] = this.lines[match.line].substring(0, match.start) + this.lines[match.line].substring(match.start + match.text.length); 22518 22519 this._init(callback); 22520 return; 22521 } 22522 } 22523 22524 // no country, so try parsing it as if we were in the same country 22525 this.country = undefined; 22526 this.countryCode = this.locale.getRegion(); 22527 this._init(callback); 22528 }, 22529 22530 /** 22531 * @private 22532 * @param {function(Address):undefined} callback 22533 */ 22534 _init: function(callback) { 22535 Utils.loadData({ 22536 object: "Address", 22537 locale: new Locale(this.countryCode), 22538 name: "address.json", 22539 sync: this.sync, 22540 loadParams: this.loadParams, 22541 callback: ilib.bind(this, function(info) { 22542 if (!info || JSUtils.isEmpty(info) || !info.fields) { 22543 // load the "unknown" locale instead 22544 Utils.loadData({ 22545 object: "Address", 22546 locale: new Locale("XX"), 22547 name: "address.json", 22548 sync: this.sync, 22549 loadParams: this.loadParams, 22550 callback: ilib.bind(this, function(info) { 22551 this.info = info; 22552 this._parseAddress(); 22553 if (typeof(callback) === 'function') { 22554 callback(this); 22555 } 22556 }) 22557 }); 22558 } else { 22559 this.info = info; 22560 this._parseAddress(); 22561 if (typeof(callback) === 'function') { 22562 callback(this); 22563 } 22564 } 22565 }) 22566 }); 22567 }, 22568 22569 /** 22570 * @private 22571 */ 22572 _parseAddress: function() { 22573 // clean it up first 22574 var i, 22575 asianChars = 0, 22576 latinChars = 0, 22577 startAt, 22578 infoFields, 22579 field, 22580 pattern, 22581 matchFunction, 22582 match, 22583 fieldNumber; 22584 22585 // for locales that support both latin and asian character addresses, 22586 // decide if we are parsing an asian or latin script address 22587 if (this.info && this.info.multiformat) { 22588 for (var j = 0; j < this.lines.length; j++) { 22589 var line = new IString(this.lines[j]); 22590 var it = line.charIterator(); 22591 while (it.hasNext()) { 22592 var c = it.next(); 22593 if (isIdeo(c) || 22594 CType.withinRange(c, "hangul") || 22595 CType.withinRange(c, 'katakana') || 22596 CType.withinRange(c, 'hiragana') || 22597 CType.withinRange(c, 'bopomofo')) { 22598 asianChars++; 22599 } else if (isAscii(c) && !isDigit(c)) { 22600 latinChars++; 22601 } 22602 } 22603 } 22604 22605 this.format = (asianChars >= latinChars) ? "asian" : "latin"; 22606 startAt = this.info.startAt[this.format]; 22607 infoFields = this.info.fields[this.format]; 22608 // //console.log("multiformat locale: format is now " + this.format); 22609 } else { 22610 startAt = (this.info && this.info.startAt) || "end"; 22611 infoFields = (this.info && this.info.fields) || []; 22612 } 22613 this.compare = (startAt === "end") ? this.endsWith : this.startsWith; 22614 22615 //console.log("this.lines is: " + JSON.stringify(this.lines)); 22616 22617 for (i = 0; i < infoFields.length && this.lines.length > 0; i++) { 22618 field = infoFields[i]; 22619 this.removeEmptyLines(this.lines); 22620 //console.log("Searching for field " + field.name); 22621 if (field.pattern) { 22622 if (typeof(field.pattern) === 'string') { 22623 pattern = new RegExp(field.pattern, "img"); 22624 matchFunction = this.matchRegExp; 22625 } else { 22626 pattern = field.pattern; 22627 matchFunction = this.matchPattern; 22628 } 22629 22630 switch (field.line) { 22631 case 'startAtFirst': 22632 for (fieldNumber = 0; fieldNumber < this.lines.length; fieldNumber++) { 22633 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 22634 if (match) { 22635 break; 22636 } 22637 } 22638 break; 22639 case 'startAtLast': 22640 for (fieldNumber = this.lines.length-1; fieldNumber >= 0; fieldNumber--) { 22641 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 22642 if (match) { 22643 break; 22644 } 22645 } 22646 break; 22647 case 'first': 22648 fieldNumber = 0; 22649 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 22650 break; 22651 case 'last': 22652 default: 22653 fieldNumber = this.lines.length - 1; 22654 match = matchFunction(this, this.lines[fieldNumber], pattern, field.matchGroup, startAt); 22655 break; 22656 } 22657 if (match) { 22658 // //console.log("found match for " + field.name + ": " + JSON.stringify(match)); 22659 // //console.log("remaining line is " + match.line); 22660 this.lines[fieldNumber] = match.line; 22661 this[field.name] = match.match; 22662 } 22663 } else { 22664 // if nothing is given, default to taking the whole field 22665 this[field.name] = this.lines.splice(fieldNumber,1)[0].trim(); 22666 //console.log("typeof(this[field.name]) is " + typeof(this[field.name]) + " and value is " + JSON.stringify(this[field.name])); 22667 } 22668 } 22669 22670 // all the left overs go in the street address field 22671 this.removeEmptyLines(this.lines); 22672 if (this.lines.length > 0) { 22673 //console.log("this.lines is " + JSON.stringify(this.lines) + " and splicing to get streetAddress"); 22674 // Korea uses spaces between words, despite being an "asian" locale 22675 var joinString = (this.info.joinString && this.info.joinString[this.format]) || ((this.format && this.format === "asian") ? "" : ", "); 22676 this.streetAddress = this.lines.join(joinString).trim(); 22677 } 22678 22679 this.lines = undefined; 22680 //console.log("final result is " + JSON.stringify(this)); 22681 }, 22682 22683 /** 22684 * @protected 22685 * Find the named country either at the end or the beginning of the address. 22686 */ 22687 _findCountry: function(name) { 22688 var start = -1, match, line = 0; 22689 22690 if (this.lines.length > 0) { 22691 start = this.startsWith(this.lines[line], name); 22692 if (start === -1) { 22693 line = this.lines.length-1; 22694 start = this.endsWith(this.lines[line], name); 22695 } 22696 if (start !== -1) { 22697 match = { 22698 text: this.lines[line].substring(start, start + name.length), 22699 line: line, 22700 start: start 22701 }; 22702 } 22703 } 22704 22705 return match; 22706 }, 22707 22708 endsWith: function (subject, query) { 22709 var start = subject.length-query.length, 22710 i, 22711 pat; 22712 //console.log("endsWith: checking " + query + " against " + subject); 22713 for (i = 0; i < query.length; i++) { 22714 // TODO: use case mapper instead of toLowerCase() 22715 if (subject.charAt(start+i).toLowerCase() !== query.charAt(i).toLowerCase()) { 22716 return -1; 22717 } 22718 } 22719 if (start > 0) { 22720 pat = /\s/; 22721 if (!pat.test(subject.charAt(start-1))) { 22722 // make sure if we are not at the beginning of the string, that the match is 22723 // not the end of some other word 22724 return -1; 22725 } 22726 } 22727 return start; 22728 }, 22729 22730 startsWith: function (subject, query) { 22731 var i; 22732 // //console.log("startsWith: checking " + query + " against " + subject); 22733 for (i = 0; i < query.length; i++) { 22734 // TODO: use case mapper instead of toLowerCase() 22735 if (subject.charAt(i).toLowerCase() !== query.charAt(i).toLowerCase()) { 22736 return -1; 22737 } 22738 } 22739 return 0; 22740 }, 22741 22742 removeEmptyLines: function (arr) { 22743 var i = 0; 22744 22745 while (i < arr.length) { 22746 if (arr[i]) { 22747 arr[i] = arr[i].trim(); 22748 if (arr[i].length === 0) { 22749 arr.splice(i,1); 22750 } else { 22751 i++; 22752 } 22753 } else { 22754 arr.splice(i,1); 22755 } 22756 } 22757 }, 22758 22759 matchRegExp: function(address, line, expression, matchGroup, startAt) { 22760 var lastMatch, 22761 match, 22762 ret = {}, 22763 last; 22764 22765 //console.log("searching for regexp " + expression.source + " in line " + line); 22766 22767 match = expression.exec(line); 22768 if (startAt === 'end') { 22769 while (match !== null && match.length > 0) { 22770 //console.log("found matches " + JSON.stringify(match)); 22771 lastMatch = match; 22772 match = expression.exec(line); 22773 } 22774 match = lastMatch; 22775 } 22776 22777 if (match && match !== null) { 22778 //console.log("found matches " + JSON.stringify(match)); 22779 matchGroup = matchGroup || 0; 22780 if (match[matchGroup] !== undefined) { 22781 ret.match = match[matchGroup].trim(); 22782 ret.match = ret.match.replace(/^\-|\-+$/, ''); 22783 ret.match = ret.match.replace(/\s+$/, ''); 22784 last = (startAt === 'end') ? line.lastIndexOf(match[matchGroup]) : line.indexOf(match[matchGroup]); 22785 //console.log("last is " + last); 22786 ret.line = line.slice(0,last); 22787 if (address.format !== "asian") { 22788 ret.line += " "; 22789 } 22790 ret.line += line.slice(last+match[matchGroup].length); 22791 ret.line = ret.line.trim(); 22792 //console.log("found match " + ret.match + " from matchgroup " + matchGroup + " and rest of line is " + ret.line); 22793 return ret; 22794 } 22795 //} else { 22796 //console.log("no match"); 22797 } 22798 22799 return undefined; 22800 }, 22801 22802 matchPattern: function(address, line, pattern, matchGroup) { 22803 var start, 22804 j, 22805 ret = {}; 22806 22807 //console.log("searching in line " + line); 22808 22809 // search an array of possible fixed strings 22810 //console.log("Using fixed set of strings."); 22811 for (j = 0; j < pattern.length; j++) { 22812 start = address.compare(line, pattern[j]); 22813 if (start !== -1) { 22814 ret.match = line.substring(start, start+pattern[j].length); 22815 if (start !== 0) { 22816 ret.line = line.substring(0,start).trim(); 22817 } else { 22818 ret.line = line.substring(pattern[j].length).trim(); 22819 } 22820 //console.log("found match " + ret.match + " and rest of line is " + ret.line); 22821 return ret; 22822 } 22823 } 22824 22825 return undefined; 22826 } 22827 }; 22828 22829 22830 22831 /*< AddressFmt.js */ 22832 /* 22833 * AddressFmt.js - Format an address 22834 * 22835 * Copyright © 2013-2015, JEDLSoft 22836 * 22837 * Licensed under the Apache License, Version 2.0 (the "License"); 22838 * you may not use this file except in compliance with the License. 22839 * You may obtain a copy of the License at 22840 * 22841 * http://www.apache.org/licenses/LICENSE-2.0 22842 * 22843 * Unless required by applicable law or agreed to in writing, software 22844 * distributed under the License is distributed on an "AS IS" BASIS, 22845 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 22846 * 22847 * See the License for the specific language governing permissions and 22848 * limitations under the License. 22849 */ 22850 22851 /* !depends 22852 ilib.js 22853 Locale.js 22854 Address.js 22855 IString.js 22856 Utils.js 22857 JSUtils.js 22858 */ 22859 22860 // !data address addressres regionnames 22861 22862 22863 22864 /** 22865 * @class 22866 * Create a new formatter object to format physical addresses in a particular way. 22867 * 22868 * The options object may contain the following properties, both of which are optional: 22869 * 22870 * <ul> 22871 * <li><i>locale</i> - the locale to use to format this address. If not specified, it uses the default locale 22872 * 22873 * <li><i>style</i> - the style of this address. The default style for each country usually includes all valid 22874 * fields for that country. 22875 * 22876 * <li><i>onLoad</i> - a callback function to call when the address info for the 22877 * locale is fully loaded and the address has been parsed. When the onLoad 22878 * option is given, the address formatter object 22879 * will attempt to load any missing locale data using the ilib loader callback. 22880 * When the constructor is done (even if the data is already preassembled), the 22881 * onLoad function is called with the current instance as a parameter, so this 22882 * callback can be used with preassembled or dynamic loading or a mix of the two. 22883 * 22884 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 22885 * asynchronously. If this option is given as "false", then the "onLoad" 22886 * callback must be given, as the instance returned from this constructor will 22887 * not be usable for a while. 22888 * 22889 * <li><i>loadParams</i> - an object containing parameters to pass to the 22890 * loader callback function when locale data is missing. The parameters are not 22891 * interpretted or modified in any way. They are simply passed along. The object 22892 * may contain any property/value pairs as long as the calling code is in 22893 * agreement with the loader callback function as to what those parameters mean. 22894 * </ul> 22895 * 22896 * 22897 * @constructor 22898 * @param {Object} options options that configure how this formatter should work 22899 * Returns a formatter instance that can format multiple addresses. 22900 */ 22901 var AddressFmt = function(options) { 22902 this.sync = true; 22903 this.styleName = 'default'; 22904 this.loadParams = {}; 22905 this.locale = new Locale(); 22906 22907 if (options) { 22908 if (options.locale) { 22909 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 22910 } 22911 22912 if (typeof(options.sync) !== 'undefined') { 22913 this.sync = !!options.sync; 22914 } 22915 22916 if (options.style) { 22917 this.styleName = options.style; 22918 } 22919 22920 if (options.loadParams) { 22921 this.loadParams = options.loadParams; 22922 } 22923 } 22924 22925 // console.log("Creating formatter for region: " + this.locale.region); 22926 Utils.loadData({ 22927 name: "address.json", 22928 object: "AddressFmt", 22929 locale: this.locale, 22930 sync: this.sync, 22931 loadParams: this.loadParams, 22932 callback: ilib.bind(this, function(info) { 22933 if (!info || JSUtils.isEmpty(info)) { 22934 // load the "unknown" locale instead 22935 Utils.loadData({ 22936 name: "address.json", 22937 object: "AddressFmt", 22938 locale: new Locale("XX"), 22939 sync: this.sync, 22940 loadParams: this.loadParams, 22941 callback: ilib.bind(this, function(info) { 22942 this.info = info; 22943 this._init(); 22944 if (options && typeof(options.onLoad) === 'function') { 22945 options.onLoad(this); 22946 } 22947 }) 22948 }); 22949 } else { 22950 this.info = info; 22951 this._init(); 22952 if (options && typeof(options.onLoad) === 'function') { 22953 options.onLoad(this); 22954 } 22955 } 22956 }) 22957 }); 22958 }; 22959 22960 /** 22961 * @private 22962 */ 22963 AddressFmt.prototype._init = function () { 22964 this.style = this.info && this.info.formats && this.info.formats[this.styleName]; 22965 22966 // use generic default -- should not happen, but just in case... 22967 this.style = this.style || (this.info && this.info.formats && this.info.formats["default"]) || "{streetAddress}\n{locality} {region} {postalCode}\n{country}"; 22968 }; 22969 22970 /** 22971 * This function formats a physical address (Address instance) for display. 22972 * Whitespace is trimmed from the beginning and end of final resulting string, and 22973 * multiple consecutive whitespace characters in the middle of the string are 22974 * compressed down to 1 space character. 22975 * 22976 * If the Address instance is for a locale that is different than the locale for this 22977 * formatter, then a hybrid address is produced. The country name is located in the 22978 * correct spot for the current formatter's locale, but the rest of the fields are 22979 * formatted according to the default style of the locale of the actual address. 22980 * 22981 * Example: a mailing address in China, but formatted for the US might produce the words 22982 * "People's Republic of China" in English at the last line of the address, and the 22983 * Chinese-style address will appear in the first line of the address. In the US, the 22984 * country is on the last line, but in China the country is usually on the first line. 22985 * 22986 * @param {Address} address Address to format 22987 * @returns {string} Returns a string containing the formatted address 22988 */ 22989 AddressFmt.prototype.format = function (address) { 22990 var ret, template, other, format; 22991 22992 if (!address) { 22993 return ""; 22994 } 22995 // console.log("formatting address: " + JSON.stringify(address)); 22996 if (address.countryCode && 22997 address.countryCode !== this.locale.region && 22998 Locale._isRegionCode(this.locale.region) && 22999 this.locale.region !== "XX") { 23000 // we are formatting an address that is sent from this country to another country, 23001 // so only the country should be in this locale, and the rest should be in the other 23002 // locale 23003 // console.log("formatting for another locale. Loading in its settings: " + address.countryCode); 23004 other = new AddressFmt({ 23005 locale: new Locale(address.countryCode), 23006 style: this.styleName 23007 }); 23008 return other.format(address); 23009 } 23010 23011 if (typeof(this.style) === 'object') { 23012 format = this.style[address.format || "latin"]; 23013 } else { 23014 format = this.style; 23015 } 23016 23017 // console.log("Using format: " + format); 23018 // make sure we have a blank string for any missing parts so that 23019 // those template parts get blanked out 23020 var params = { 23021 country: address.country || "", 23022 region: address.region || "", 23023 locality: address.locality || "", 23024 streetAddress: address.streetAddress || "", 23025 postalCode: address.postalCode || "", 23026 postOffice: address.postOffice || "" 23027 }; 23028 template = new IString(format); 23029 ret = template.format(params); 23030 ret = ret.replace(/[ \t]+/g, ' '); 23031 ret = ret.replace("\n ", "\n"); 23032 ret = ret.replace(" \n", "\n"); 23033 return ret.replace(/\n+/g, '\n').trim(); 23034 }; 23035 23036 23037 /** 23038 * Return true if this is an asian locale. 23039 * @private 23040 * @returns {boolean} true if this is an asian locale, or false otherwise 23041 */ 23042 function isAsianLocale(locale) { 23043 return locale.language === "zh" || locale.language === "ja" || locale.language === "ko"; 23044 } 23045 23046 /** 23047 * Invert the properties and values, filtering out all the regions. Regions either 23048 * have values with numbers (eg. "150" for Europe), or they are on a short list of 23049 * known regions with actual ISO codes. 23050 * 23051 * @private 23052 * @returns {Object} the inverted object 23053 */ 23054 function invertAndFilter(object) { 23055 var ret = []; 23056 var regions = ["AQ", "EU", "EZ", "UN", "ZZ"] 23057 for (var p in object) { 23058 if (p && !object[p].match(/\d/) && regions.indexOf(object[p]) === -1) { 23059 ret.push({ 23060 code: object[p], 23061 name: p 23062 }); 23063 } 23064 } 23065 23066 return ret; 23067 } 23068 23069 /** 23070 * Return information about the address format that can be used 23071 * by UI builders to display a locale-sensitive set of input fields 23072 * based on the current formatter's settings.<p> 23073 * 23074 * The object returned by this method is an array of address rows. Each 23075 * row is itself an array which may have one to four address 23076 * components in that row. Each address component is an object 23077 * that contains a component property and a label to display 23078 * with it. The label is written in the given locale, or the 23079 * locale of this formatter if it was not given.<p> 23080 * 23081 * Optionally, if the address component is constrained to a 23082 * particular pattern or to a fixed list of possible values, then 23083 * the constraint rules are given in the "constraint" property.<p> 23084 * 23085 * If an address component must conform to a particular pattern, 23086 * the regular expression that matches that pattern 23087 * is returned in "constraint". Mostly, it is only the postal code 23088 * component that can be validated in this way.<p> 23089 * 23090 * If an address component should be limited 23091 * to a fixed list of values, then the constraint property will be 23092 * set to an array that lists those values. The constraint contains 23093 * an array of objects in the correct sorted order for the locale 23094 * where each object contains an code property containing the ISO code, 23095 * and a name field to show in UI. 23096 * The ISO codes should not be shown to the user and are intended to 23097 * represent the values in code. The names are translated to the given 23098 * locale or to the locale of this formatter if it was not given. For 23099 * the most part, it is the region and country components that 23100 * are constrained in this way.<p> 23101 * 23102 * Here is what the result would look like for a US address: 23103 * <pre> 23104 * [ 23105 * [{ 23106 * "component": "streetAddress", 23107 * "label": "Street Address" 23108 * }], 23109 * [{ 23110 * "component": "locality", 23111 * "label": "City" 23112 * },{ 23113 * "component": "region", 23114 * "label": "State", 23115 * "constraint": [{ 23116 * "code": "AL", 23117 * "name": "Alabama" 23118 * },{ 23119 * "code": "AK", 23120 * "name": "Alaska" 23121 * },{ 23122 * ... 23123 * },{ 23124 * "code": "WY", 23125 * "name": "Wyoming" 23126 * } 23127 * },{ 23128 * "component": "postalCode", 23129 * "label": "Zip Code", 23130 * "constraint": "[0-9]{5}(-[0-9]{4})?" 23131 * }], 23132 * [{ 23133 * "component": "country", 23134 * "label": "Country", 23135 * "constraint": [{ 23136 * "code": "AF", 23137 * "name": "Afghanistan" 23138 * },{ 23139 * "code": "AL", 23140 * "name": "Albania" 23141 * },{ 23142 * ... 23143 * },{ 23144 * "code": "ZW", 23145 * "name": "Zimbabwe" 23146 * }] 23147 * }] 23148 * ] 23149 * </pre> 23150 * <p> 23151 * @example <caption>Example of calling the getFormatInfo method</caption> 23152 * 23153 * // the AddressFmt should be created with the locale of the address you 23154 * // would like the user to enter. For example, if you have a "country" 23155 * // selector, you would create a new AddressFmt instance each time the 23156 * // selector is changed. 23157 * new AddressFmt({ 23158 * locale: 'nl-NL', // for addresses in the Netherlands 23159 * onLoad: ilib.bind(this, function(fmt) { 23160 * fmt.getAddressFormatInfo({ 23161 * // The following is the locale of the UI you would like to see the labels 23162 * // like "City" and "Postal Code" translated to. In this example, we 23163 * // are showing an input form for Dutch addresses, but the labels are 23164 * // written in US English. 23165 * locale: "en-US", 23166 * onLoad: ilib.bind(this, function(rows) { 23167 * // iterate through the rows array and dynamically create the input 23168 * // elements with the given labels 23169 * }) 23170 * }); 23171 * }) 23172 * }); 23173 * 23174 * @param {Locale|string=} locale the locale to translate the labels 23175 * to. If not given, the locale of the formatter will be used. 23176 * @param {boolean=} sync true if this method should load the data 23177 * synchronously, false if async 23178 * @param {Function=} callback a callback to call when the data 23179 * is ready 23180 * @returns {Array.<Object>} An array of rows of address components 23181 */ 23182 AddressFmt.prototype.getFormatInfo = function(locale, sync, callback) { 23183 var info; 23184 var loc = new Locale(this.locale); 23185 if (locale) { 23186 if (typeof(locale) === "string") { 23187 locale = new Locale(locale); 23188 } 23189 loc.language = locale.getLanguage(); 23190 loc.spec = undefined; 23191 } 23192 23193 Utils.loadData({ 23194 name: "regionnames.json", 23195 object: "AddressFmt", 23196 locale: loc, 23197 sync: this.sync, 23198 loadParams: JSUtils.merge(this.loadParams, {returnOne: true}, true), 23199 callback: ilib.bind(this, function(regions) { 23200 this.regions = regions; 23201 23202 new ResBundle({ 23203 locale: loc, 23204 name: "addressres", 23205 sync: this.sync, 23206 loadParams: this.loadParams, 23207 onLoad: ilib.bind(this, function (rb) { 23208 var type, format, fields = this.info.fields; 23209 if (this.info.multiformat) { 23210 type = isAsianLocale(this.locale) ? "asian" : "latin"; 23211 fields = this.info.fields[type]; 23212 } 23213 23214 if (typeof(this.style) === 'object') { 23215 format = this.style[type || "latin"]; 23216 } else { 23217 format = this.style; 23218 } 23219 new Address(" ", { 23220 locale: loc, 23221 sync: this.sync, 23222 loadParams: this.loadParams, 23223 onLoad: ilib.bind(this, function(localeAddress) { 23224 var rows = format.split(/\n/g); 23225 info = rows.map(ilib.bind(this, function(row) { 23226 return row.split("}").filter(function(component) { 23227 return component.length > 0; 23228 }).map(ilib.bind(this, function(component) { 23229 var name = component.replace(/.*{/, ""); 23230 var obj = { 23231 component: name, 23232 label: rb.getStringJS(this.info.fieldNames[name]) 23233 }; 23234 var field = fields.filter(function(f) { 23235 return f.name === name; 23236 }); 23237 if (field && field[0] && field[0].pattern) { 23238 if (typeof(field[0].pattern) === "string") { 23239 obj.constraint = field[0].pattern; 23240 } 23241 } 23242 if (name === "country") { 23243 obj.constraint = invertAndFilter(localeAddress.ctrynames); 23244 } else if (name === "region" && this.regions[loc.getRegion()]) { 23245 obj.constraint = this.regions[loc.getRegion()]; 23246 } 23247 return obj; 23248 })); 23249 })); 23250 23251 if (callback && typeof(callback) === "function") { 23252 callback(info); 23253 } 23254 }) 23255 }); 23256 }) 23257 }); 23258 }) 23259 }); 23260 23261 return info; 23262 }; 23263 23264 23265 23266 /*< GlyphString.js */ 23267 /* 23268 * GlyphString.js - ilib string subclass that allows you to access 23269 * whole glyphs at a time 23270 * 23271 * Copyright © 2015-2018, JEDLSoft 23272 * 23273 * Licensed under the Apache License, Version 2.0 (the "License"); 23274 * you may not use this file except in compliance with the License. 23275 * You may obtain a copy of the License at 23276 * 23277 * http://www.apache.org/licenses/LICENSE-2.0 23278 * 23279 * Unless required by applicable law or agreed to in writing, software 23280 * distributed under the License is distributed on an "AS IS" BASIS, 23281 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23282 * 23283 * See the License for the specific language governing permissions and 23284 * limitations under the License. 23285 */ 23286 23287 // !depends IString.js CType.js Utils.js JSUtils.js 23288 // !data normdata ctype_m 23289 23290 23291 23292 /** 23293 * @class 23294 * Create a new glyph string instance. This string inherits from 23295 * the IString class, and adds methods that allow you to access 23296 * whole glyphs at a time. <p> 23297 * 23298 * In Unicode, various accented characters can be created by using 23299 * a base character and one or more combining characters following 23300 * it. These appear on the screen to the user as a single glyph. 23301 * For example, the Latin character "a" (U+0061) followed by the 23302 * combining diaresis character "¨" (U+0308) combine together to 23303 * form the "a with diaresis" glyph "ä", which looks like a single 23304 * character on the screen.<p> 23305 * 23306 * The big problem with combining characters for web developers is 23307 * that many CSS engines do not ellipsize text between glyphs. They 23308 * only deal with single Unicode characters. So if a particular space 23309 * only allows for 4 characters, the CSS engine will truncate a 23310 * string at 4 Unicode characters and then add the ellipsis (...) 23311 * character. What if the fourth Unicode character is the "a" and 23312 * the fifth one is the diaresis? Then a string like "xxxäxxx" that 23313 * is ellipsized at 4 characters will appear as "xxxa..." on the 23314 * screen instead of "xxxä...".<p> 23315 * 23316 * In the Latin script as it is commonly used, it is not so common 23317 * to form accented characters using combining accents, so the above 23318 * example is mostly for illustrative purposes. It is not unheard of 23319 * however. The situation is much, much worse in scripts such as Thai and 23320 * Devanagari that normally make very heavy use of combining characters. 23321 * These scripts do so because Unicode does not include pre-composed 23322 * versions of the accented characters like it does for Latin, so 23323 * combining accents are the only way to create these accented and 23324 * combined versions of the characters.<p> 23325 * 23326 * The solution to this problem is not to use the the CSS property 23327 * "text-overflow: ellipsis" in your web site, ever. Instead, use 23328 * a glyph string to truncate text between glyphs dynamically, 23329 * rather than truncating between Unicode characters using CSS.<p> 23330 * 23331 * Glyph strings are also useful for truncation, hyphenation, and 23332 * line wrapping, as all of these should be done between glyphs instead 23333 * of between characters.<p> 23334 * 23335 * The options parameter is optional, and may contain any combination 23336 * of the following properties:<p> 23337 * 23338 * <ul> 23339 * <li><i>onLoad</i> - a callback function to call when the locale data are 23340 * fully loaded. When the onLoad option is given, this object will attempt to 23341 * load any missing locale data using the ilib loader callback. 23342 * When the constructor is done (even if the data is already preassembled), the 23343 * onLoad function is called with the current instance as a parameter, so this 23344 * callback can be used with preassembled or dynamic loading or a mix of the two. 23345 * 23346 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 23347 * asynchronously. If this option is given as "false", then the "onLoad" 23348 * callback must be given, as the instance returned from this constructor will 23349 * not be usable for a while. 23350 * 23351 * <li><i>loadParams</i> - an object containing parameters to pass to the 23352 * loader callback function when locale data is missing. The parameters are not 23353 * interpretted or modified in any way. They are simply passed along. The object 23354 * may contain any property/value pairs as long as the calling code is in 23355 * agreement with the loader callback function as to what those parameters mean. 23356 * </ul> 23357 * 23358 * @constructor 23359 * @extends IString 23360 * @param {string|IString=} str initialize this instance with this string 23361 * @param {Object=} options options governing the way this instance works 23362 */ 23363 var GlyphString = function (str, options) { 23364 if (options && options.noinstance) { 23365 return; 23366 } 23367 23368 IString.call(this, str); 23369 23370 options = options || {sync: true}; 23371 23372 CType._load("ctype_m", options.sync, options.loadParams, ilib.bind(this, function() { 23373 if (!ilib.data.norm || JSUtils.isEmpty(ilib.data.norm.ccc)) { 23374 Utils.loadData({ 23375 object: "GlyphString", 23376 locale: "-", 23377 name: "normdata.json", 23378 nonlocale: true, 23379 sync: options.sync, 23380 loadParams: options.loadParams, 23381 callback: ilib.bind(this, function (norm) { 23382 ilib.extend(ilib.data.norm, norm); 23383 if (options && typeof(options.onLoad) === 'function') { 23384 options.onLoad(this); 23385 } 23386 }) 23387 }); 23388 } else { 23389 if (options && typeof(options.onLoad) === 'function') { 23390 options.onLoad(this); 23391 } 23392 } 23393 })); 23394 }; 23395 23396 GlyphString.prototype = new IString(undefined); 23397 GlyphString.prototype.parent = IString; 23398 GlyphString.prototype.constructor = GlyphString; 23399 23400 /** 23401 * Return true if the given character is a leading Jamo (Choseong) character. 23402 * 23403 * @private 23404 * @static 23405 * @param {number} n code point to check 23406 * @return {boolean} true if the character is a leading Jamo character, 23407 * false otherwise 23408 */ 23409 GlyphString._isJamoL = function (n) { 23410 return (n >= 0x1100 && n <= 0x1112); 23411 }; 23412 23413 /** 23414 * Return true if the given character is a vowel Jamo (Jungseong) character. 23415 * 23416 * @private 23417 * @static 23418 * @param {number} n code point to check 23419 * @return {boolean} true if the character is a vowel Jamo character, 23420 * false otherwise 23421 */ 23422 GlyphString._isJamoV = function (n) { 23423 return (n >= 0x1161 && n <= 0x1175); 23424 }; 23425 23426 /** 23427 * Return true if the given character is a trailing Jamo (Jongseong) character. 23428 * 23429 * @private 23430 * @static 23431 * @param {number} n code point to check 23432 * @return {boolean} true if the character is a trailing Jamo character, 23433 * false otherwise 23434 */ 23435 GlyphString._isJamoT = function (n) { 23436 return (n >= 0x11A8 && n <= 0x11C2); 23437 }; 23438 23439 /** 23440 * Return true if the given character is a LV Jamo character. 23441 * LV Jamo character is a precomposed Hangul character with LV sequence. 23442 * 23443 * @private 23444 * @static 23445 * @param {number} n code point to check 23446 * @return {boolean} true if the character is a LV Jamo character, 23447 * false otherwise 23448 */ 23449 GlyphString._isJamoLV = function (n) { 23450 var syllableBase = 0xAC00; 23451 var leadingJamoCount = 19; 23452 var vowelJamoCount = 21; 23453 var trailingJamoCount = 28; 23454 var syllableCount = leadingJamoCount * vowelJamoCount * trailingJamoCount; 23455 var syllableIndex = n - syllableBase; 23456 // Check if n is a precomposed Hangul 23457 if (0 <= syllableIndex && syllableIndex < syllableCount) { 23458 // Check if n is a LV Jamo character 23459 if((syllableIndex % trailingJamoCount) == 0) { 23460 return true; 23461 } 23462 } 23463 return false; 23464 }; 23465 23466 /** 23467 * Return true if the given character is a precomposed Hangul character. 23468 * The precomposed Hangul character may be a LV Jamo character or a LVT Jamo Character. 23469 * 23470 * @private 23471 * @static 23472 * @param {number} n code point to check 23473 * @return {boolean} true if the character is a precomposed Hangul character, 23474 * false otherwise 23475 */ 23476 GlyphString._isHangul = function (n) { 23477 return (n >= 0xAC00 && n <= 0xD7A3); 23478 }; 23479 23480 /** 23481 * Algorithmically compose an L and a V combining Jamo characters into 23482 * a precomposed Korean syllabic Hangul character. Both should already 23483 * be in the proper ranges for L and V characters. 23484 * 23485 * @private 23486 * @static 23487 * @param {number} lead the code point of the lead Jamo character to compose 23488 * @param {number} trail the code point of the trailing Jamo character to compose 23489 * @return {string} the composed Hangul character 23490 */ 23491 GlyphString._composeJamoLV = function (lead, trail) { 23492 var lindex = lead - 0x1100; 23493 var vindex = trail - 0x1161; 23494 return IString.fromCodePoint(0xAC00 + (lindex * 21 + vindex) * 28); 23495 }; 23496 23497 /** 23498 * Algorithmically compose a Hangul LV and a combining Jamo T character 23499 * into a precomposed Korean syllabic Hangul character. 23500 * 23501 * @private 23502 * @static 23503 * @param {number} lead the code point of the lead Hangul character to compose 23504 * @param {number} trail the code point of the trailing Jamo T character to compose 23505 * @return {string} the composed Hangul character 23506 */ 23507 GlyphString._composeJamoLVT = function (lead, trail) { 23508 return IString.fromCodePoint(lead + (trail - 0x11A7)); 23509 }; 23510 23511 /** 23512 * Compose one character out of a leading character and a 23513 * trailing character. If the characters are Korean Jamo, they 23514 * will be composed algorithmically. If they are any other 23515 * characters, they will be looked up in the nfc tables. 23516 * 23517 * @private 23518 * @static 23519 * @param {string} lead leading character to compose 23520 * @param {string} trail the trailing character to compose 23521 * @return {string|null} the fully composed character, or undefined if 23522 * there is no composition for those two characters 23523 */ 23524 GlyphString._compose = function (lead, trail) { 23525 var first = lead.charCodeAt(0); 23526 var last = trail.charCodeAt(0); 23527 if (GlyphString._isJamoLV(first) && GlyphString._isJamoT(last)) { 23528 return GlyphString._composeJamoLVT(first, last); 23529 } else if (GlyphString._isJamoL(first) && GlyphString._isJamoV(last)) { 23530 return GlyphString._composeJamoLV(first, last); 23531 } 23532 23533 var c = lead + trail; 23534 return (ilib.data.norm.nfc && ilib.data.norm.nfc[c]); 23535 }; 23536 23537 /** 23538 * Return an iterator that will step through all of the characters 23539 * in the string one at a time, taking care to step through decomposed 23540 * characters and through surrogate pairs in the UTF-16 encoding 23541 * as single characters. <p> 23542 * 23543 * The GlyphString class will return decomposed Unicode characters 23544 * as a single unit that a user might see on the screen as a single 23545 * glyph. If the 23546 * next character in the iteration is a base character and it is 23547 * followed by combining characters, the base and all its following 23548 * combining characters are returned as a single unit.<p> 23549 * 23550 * The standard Javascript String's charAt() method only 23551 * returns information about a particular 16-bit character in the 23552 * UTF-16 encoding scheme. 23553 * If the index is pointing to a low- or high-surrogate character, 23554 * it will return that surrogate character rather 23555 * than the surrogate pair which represents a character 23556 * in the supplementary planes.<p> 23557 * 23558 * The iterator instance returned has two methods, hasNext() which 23559 * returns true if the iterator has more characters to iterate through, 23560 * and next() which returns the next character.<p> 23561 * 23562 * @override 23563 * @return {Object} an iterator 23564 * that iterates through all the characters in the string 23565 */ 23566 GlyphString.prototype.charIterator = function() { 23567 var it = IString.prototype.charIterator.call(this); 23568 23569 /** 23570 * @constructor 23571 */ 23572 function _chiterator (istring) { 23573 this.index = 0; 23574 this.spacingCombining = false; 23575 this.hasNext = function () { 23576 return !!this.nextChar || it.hasNext(); 23577 }; 23578 this.next = function () { 23579 var ch = this.nextChar || it.next(), 23580 prevCcc = ilib.data.norm.ccc[ch], 23581 nextCcc, 23582 composed = ch; 23583 23584 this.nextChar = undefined; 23585 this.spacingCombining = false; 23586 23587 if (ilib.data.norm.ccc && 23588 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ilib.data.norm.ccc[ch] === 0)) { 23589 // found a starter... find all the non-starters until the next starter. Must include 23590 // the next starter because under some odd circumstances, two starters sometimes recompose 23591 // together to form another character 23592 var notdone = true; 23593 while (it.hasNext() && notdone) { 23594 this.nextChar = it.next(); 23595 nextCcc = ilib.data.norm.ccc[this.nextChar]; 23596 var codePoint = IString.toCodePoint(this.nextChar, 0); 23597 // Mn characters are Marks that are non-spacing. These do not take more room than an accent, so they should be 23598 // considered part of the on-screen glyph, even if they are non-combining. Mc are marks that are spacing 23599 // and combining, which means they are part of the glyph, but they cause the glyph to use up more space than 23600 // just the base character alone. 23601 var isMn = CType._inRange(codePoint, "Mn", ilib.data.ctype_m); 23602 var isMc = CType._inRange(codePoint, "Mc", ilib.data.ctype_m); 23603 if (isMn || isMc || (typeof(nextCcc) !== 'undefined' && nextCcc !== 0)) { 23604 if (isMc) { 23605 this.spacingCombining = true; 23606 } 23607 ch += this.nextChar; 23608 this.nextChar = undefined; 23609 } else { 23610 // found the next starter. See if this can be composed with the previous starter 23611 var testChar = GlyphString._compose(composed, this.nextChar); 23612 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 23613 // not blocked and there is a mapping 23614 composed = testChar; 23615 ch += this.nextChar; 23616 this.nextChar = undefined; 23617 } else { 23618 // finished iterating, leave this.nextChar for the next next() call 23619 notdone = false; 23620 } 23621 } 23622 prevCcc = nextCcc; 23623 } 23624 } 23625 return ch; 23626 }; 23627 // Returns true if the last character returned by the "next" method included 23628 // spacing combining characters. If it does, then the character was wider than 23629 // just the base character alone, and the truncation code will not add it. 23630 this.wasSpacingCombining = function() { 23631 return this.spacingCombining; 23632 }; 23633 }; 23634 return new _chiterator(this); 23635 }; 23636 23637 /** 23638 * Truncate the current string at the given number of whole glyphs and return 23639 * the resulting string. 23640 * 23641 * @param {number} length the number of whole glyphs to keep in the string 23642 * @return {string} a string truncated to the requested number of glyphs 23643 */ 23644 GlyphString.prototype.truncate = function(length) { 23645 var it = this.charIterator(); 23646 var tr = ""; 23647 for (var i = 0; i < length-1 && it.hasNext(); i++) { 23648 tr += it.next(); 23649 } 23650 23651 /* 23652 * handle the last character separately. If it contains spacing combining 23653 * accents, then we must assume that it uses up more horizontal space on 23654 * the screen than just the base character by itself, and therefore this 23655 * method will not truncate enough characters to fit in the given length. 23656 * In this case, we have to chop off not only the combining characters, 23657 * but also the base character as well because the base without the 23658 * combining accents is considered a different character. 23659 */ 23660 if (i < length && it.hasNext()) { 23661 var c = it.next(); 23662 if (!it.wasSpacingCombining()) { 23663 tr += c; 23664 } 23665 } 23666 return tr; 23667 }; 23668 23669 /** 23670 * Truncate the current string at the given number of glyphs and add an ellipsis 23671 * to indicate that is more to the string. The ellipsis forms the last character 23672 * in the string, so the string is actually truncated at length-1 glyphs. 23673 * 23674 * @param {number} length the number of whole glyphs to keep in the string 23675 * including the ellipsis 23676 * @return {string} a string truncated to the requested number of glyphs 23677 * with an ellipsis 23678 */ 23679 GlyphString.prototype.ellipsize = function(length) { 23680 return this.truncate(length > 0 ? length-1 : 0) + "…"; 23681 }; 23682 23683 23684 23685 /*< NormString.js */ 23686 /* 23687 * NormString.js - ilib normalized string subclass definition 23688 * 23689 * Copyright © 2013-2015, JEDLSoft 23690 * 23691 * Licensed under the Apache License, Version 2.0 (the "License"); 23692 * you may not use this file except in compliance with the License. 23693 * You may obtain a copy of the License at 23694 * 23695 * http://www.apache.org/licenses/LICENSE-2.0 23696 * 23697 * Unless required by applicable law or agreed to in writing, software 23698 * distributed under the License is distributed on an "AS IS" BASIS, 23699 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 23700 * 23701 * See the License for the specific language governing permissions and 23702 * limitations under the License. 23703 */ 23704 23705 // !depends IString.js GlyphString.js Utils.js 23706 23707 23708 23709 /** 23710 * @class 23711 * Create a new normalized string instance. This string inherits from 23712 * the GlyphString class, and adds the normalize method. It can be 23713 * used anywhere that a normal Javascript string is used. <p> 23714 * 23715 * The options parameter is optional, and may contain any combination 23716 * of the following properties:<p> 23717 * 23718 * <ul> 23719 * <li><i>onLoad</i> - a callback function to call when the locale data are 23720 * fully loaded. When the onLoad option is given, this object will attempt to 23721 * load any missing locale data using the ilib loader callback. 23722 * When the constructor is done (even if the data is already preassembled), the 23723 * onLoad function is called with the current instance as a parameter, so this 23724 * callback can be used with preassembled or dynamic loading or a mix of the two. 23725 * 23726 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 23727 * asynchronously. If this option is given as "false", then the "onLoad" 23728 * callback must be given, as the instance returned from this constructor will 23729 * not be usable for a while. 23730 * 23731 * <li><i>loadParams</i> - an object containing parameters to pass to the 23732 * loader callback function when locale data is missing. The parameters are not 23733 * interpretted or modified in any way. They are simply passed along. The object 23734 * may contain any property/value pairs as long as the calling code is in 23735 * agreement with the loader callback function as to what those parameters mean. 23736 * </ul> 23737 * 23738 * @constructor 23739 * @extends GlyphString 23740 * @param {string|IString=} str initialize this instance with this string 23741 * @param {Object=} options options governing the way this instance works 23742 */ 23743 var NormString = function (str, options) { 23744 GlyphString.call(this, str, options); 23745 }; 23746 23747 NormString.prototype = new GlyphString("", {noinstance:true}); 23748 NormString.prototype.parent = GlyphString; 23749 NormString.prototype.constructor = NormString; 23750 23751 /** 23752 * Initialize the normalized string routines statically. This 23753 * is intended to be called in a dynamic-load version of ilib 23754 * to load the data need to normalize strings before any instances 23755 * of NormString are created.<p> 23756 * 23757 * The options parameter may contain any of the following properties: 23758 * 23759 * <ul> 23760 * <li><i>form</i> - {string} the normalization form to load 23761 * <li><i>script</i> - {string} load the normalization for this script. If the 23762 * script is given as "all" then the normalization data for all scripts 23763 * is loaded at the same time 23764 * <li><i>sync</i> - {boolean} whether to load the files synchronously or not 23765 * <li><i>loadParams</i> - {Object} parameters to the loader function 23766 * <li><i>onLoad</i> - {function()} a function to call when the 23767 * files are done being loaded 23768 * </ul> 23769 * 23770 * @param {Object} options an object containing properties that govern 23771 * how to initialize the data 23772 */ 23773 23774 NormString.init = function(options) { 23775 var form = "nfkc"; 23776 var script = "all"; 23777 var sync = true; 23778 var loadParams = undefined; 23779 23780 if (options) { 23781 if (options.form) { 23782 form = options.form; 23783 } 23784 if (options.script) { 23785 script = options.script; 23786 } 23787 if (options.loadParams) { 23788 loadParams = options.loadParams; 23789 } 23790 if (typeof(options.sync) === 'boolean') { 23791 sync = options.sync; 23792 } 23793 } 23794 23795 var formDependencies = { 23796 "nfd": ["nfd"], 23797 "nfc": ["nfd"], 23798 "nfkd": ["nfkd", "nfd"], 23799 "nfkc": ["nfkd", "nfd"] 23800 }; 23801 var files = ["normdata.json"]; 23802 var forms = formDependencies[form]; 23803 for (var f in forms) { 23804 files.push(forms[f] + "/" + script + ".json"); 23805 } 23806 23807 if (!ilib.data.norm || JSUtils.isEmpty(ilib.data.norm.ccc)) { 23808 Utils.loadData({ 23809 object: "NormString", 23810 name: "normdata.json", 23811 locale: "-", 23812 nonlocale: true, 23813 sync: sync, 23814 loadParams: loadParams, 23815 callback: ilib.bind(this, function(normdata) { 23816 if (!normdata) { 23817 ilib.data.cache.normdata = normdata; 23818 } 23819 23820 if (JSUtils.isEmpty(ilib.data.norm.ccc) || JSUtils.isEmpty(ilib.data.norm.nfd) || JSUtils.isEmpty(ilib.data.norm.nfkd)) { 23821 //console.log("loading files " + JSON.stringify(files)); 23822 Utils._callLoadData(files, sync, loadParams, function(arr) { 23823 ilib.extend(ilib.data.norm, arr[0]); 23824 for (var i = 1; i < arr.length; i++) { 23825 if (typeof(arr[i]) !== 'undefined') { 23826 ilib.extend(ilib.data.norm[forms[i-1]], arr[i]); 23827 } 23828 } 23829 if (options && typeof(options.onLoad) === 'function') { 23830 options.onLoad(this); 23831 } 23832 }); 23833 } else { 23834 if (options && typeof(options.onLoad) === 'function') { 23835 options.onLoad(this); 23836 } 23837 } 23838 }) 23839 }) 23840 } else { 23841 if (options && typeof(options.onLoad) === 'function') { 23842 options.onLoad(this); 23843 } 23844 } 23845 } 23846 23847 /** 23848 * Algorithmically decompose a precomposed Korean syllabic Hangul 23849 * character into its individual combining Jamo characters. The given 23850 * character must be in the range of Hangul characters U+AC00 to U+D7A3. 23851 * 23852 * @private 23853 * @static 23854 * @param {number} cp code point of a Korean Hangul character to decompose 23855 * @return {string} the decomposed string of Jamo characters 23856 */ 23857 NormString._decomposeHangul = function (cp) { 23858 var sindex = cp - 0xAC00; 23859 var result = String.fromCharCode(0x1100 + sindex / 588) + 23860 String.fromCharCode(0x1161 + (sindex % 588) / 28); 23861 var t = sindex % 28; 23862 if (t !== 0) { 23863 result += String.fromCharCode(0x11A7 + t); 23864 } 23865 return result; 23866 }; 23867 23868 /** 23869 * Expand one character according to the given canonical and 23870 * compatibility mappings. 23871 * 23872 * @private 23873 * @static 23874 * @param {string} ch character to map 23875 * @param {Object} canon the canonical mappings to apply 23876 * @param {Object=} compat the compatibility mappings to apply, or undefined 23877 * if only the canonical mappings are needed 23878 * @return {string} the mapped character 23879 */ 23880 NormString._expand = function (ch, canon, compat) { 23881 var i, 23882 expansion = "", 23883 n = ch.charCodeAt(0); 23884 if (GlyphString._isHangul(n)) { 23885 expansion = NormString._decomposeHangul(n); 23886 } else { 23887 var result = canon[ch]; 23888 if (!result && compat) { 23889 result = compat[ch]; 23890 } 23891 if (result && result !== ch) { 23892 for (i = 0; i < result.length; i++) { 23893 expansion += NormString._expand(result[i], canon, compat); 23894 } 23895 } else { 23896 expansion = ch; 23897 } 23898 } 23899 return expansion; 23900 }; 23901 23902 /** 23903 * Perform the Unicode Normalization Algorithm upon the string and return 23904 * the resulting new string. The current string is not modified. 23905 * 23906 * <h2>Forms</h2> 23907 * 23908 * The forms of possible normalizations are defined by the <a 23909 * href="http://www.unicode.org/reports/tr15/">Unicode Standard 23910 * Annex (UAX) 15</a>. The form parameter is a string that may have one 23911 * of the following values: 23912 * 23913 * <ul> 23914 * <li>nfd - Canonical decomposition. This decomposes characters into 23915 * their exactly equivalent forms. For example, "ü" would decompose 23916 * into a "u" followed by the combining diaeresis character. 23917 * <li>nfc - Canonical decomposition followed by canonical composition. 23918 * This decomposes and then recomposes character into their shortest 23919 * exactly equivalent forms by recomposing as many combining characters 23920 * as possible. For example, "ü" followed by a combining 23921 * macron character would decompose into a "u" followed by the combining 23922 * macron characters the combining diaeresis character, and then be recomposed into 23923 * the u with macron and diaeresis "ṻ" character. The reason that 23924 * the "nfc" form decomposes and then recomposes is that combining characters 23925 * have a specific order under the Unicode Normalization Algorithm, and 23926 * partly composed characters such as the "ü" followed by combining 23927 * marks may change the order of the combining marks when decomposed and 23928 * recomposed. 23929 * <li>nfkd - Compatibility decomposition. This decomposes characters 23930 * into compatible forms that may not be exactly equivalent semantically, 23931 * as well as performing canonical decomposition as well. 23932 * For example, the "œ" ligature character decomposes to the two 23933 * characters "oe" because they are compatible even though they are not 23934 * exactly the same semantically. 23935 * <li>nfkc - Compatibility decomposition followed by canonical composition. 23936 * This decomposes characters into compatible forms, then recomposes 23937 * characters using the canonical composition. That is, it breaks down 23938 * characters into the compatible forms, and then recombines all combining 23939 * marks it can with their base characters. For example, the character 23940 * "ǽ" would be normalized to "aé" by first decomposing 23941 * the character into "a" followed by "e" followed by the combining acute accent 23942 * combining mark, and then recomposed to an "a" followed by the "e" 23943 * with acute accent. 23944 * </ul> 23945 * 23946 * <h2>Operation</h2> 23947 * 23948 * Two strings a and b can be said to be canonically equivalent if 23949 * normalize(a) = normalize(b) 23950 * under the nfc normalization form. Two strings can be said to be compatible if 23951 * normalize(a) = normalize(b) under the nfkc normalization form.<p> 23952 * 23953 * The canonical normalization is often used to see if strings are 23954 * equivalent to each other, and thus is useful when implementing parsing 23955 * algorithms or exact matching algorithms. It can also be used to ensure 23956 * that any string output produces a predictable sequence of characters.<p> 23957 * 23958 * Compatibility normalization 23959 * does not always preserve the semantic meaning of all the characters, 23960 * although this is sometimes the behaviour that you are after. It is useful, 23961 * for example, when doing searches of user-input against text in documents 23962 * where the matches are supposed to "fuzzy". In this case, both the query 23963 * string and the document string would be mapped to their compatibility 23964 * normalized forms, and then compared.<p> 23965 * 23966 * Compatibility normalization also does not guarantee round-trip conversion 23967 * to and from legacy character sets as the normalization is "lossy". It is 23968 * akin to doing a lower- or upper-case conversion on text -- after casing, 23969 * you cannot tell what case each character is in the original string. It is 23970 * good for matching and searching, but it rarely good for output because some 23971 * distinctions or meanings in the original text have been lost.<p> 23972 * 23973 * Note that W3C normalization for HTML also escapes and unescapes 23974 * HTML character entities such as "ü" for u with diaeresis. This 23975 * method does not do such escaping or unescaping. If normalization is required 23976 * for HTML strings with entities, unescaping should be performed on the string 23977 * prior to calling this method.<p> 23978 * 23979 * <h2>Data</h2> 23980 * 23981 * Normalization requires a fair amount of mapping data, much of which you may 23982 * not need for the characters expected in your texts. It is possible to assemble 23983 * a copy of ilib that saves space by only including normalization data for 23984 * those scripts that you expect to encounter in your data.<p> 23985 * 23986 * The normalization data is organized by normalization form and within there 23987 * by script. To include the normalization data for a particular script with 23988 * a particular normalization form, use the directive: 23989 * 23990 * <pre><code> 23991 * !depends <form>/<script>.js 23992 * </code></pre> 23993 * 23994 * Where <form> is the normalization form ("nfd", "nfc", "nfkd", or "nfkc"), and 23995 * <script> is the ISO 15924 code for the script you would like to 23996 * support. Example: to load in the NFC data for Cyrillic, you would use: 23997 * 23998 * <pre><code> 23999 * !depends nfc/Cyrl.js 24000 * </code></pre> 24001 * 24002 * Note that because certain normalization forms include others in their algorithm, 24003 * their data also depends on the data for the other forms. For example, if you 24004 * include the "nfc" data for a script, you will automatically get the "nfd" data 24005 * for that same script as well because the NFC algorithm does NFD normalization 24006 * first. Here are the dependencies:<p> 24007 * 24008 * <ul> 24009 * <li>NFD -> no dependencies 24010 * <li>NFC -> NFD 24011 * <li>NFKD -> NFD 24012 * <li>NFKC -> NFKD, NFD, NFC 24013 * </ul> 24014 * 24015 * A special value for the script dependency is "all" which will cause the data for 24016 * all scripts 24017 * to be loaded for that normalization form. This would be useful if you know that 24018 * you are going to normalize a lot of multilingual text or cannot predict which scripts 24019 * will appear in the input. Because the NFKC form depends on all others, you can 24020 * get all of the data for all forms automatically by depending on "nfkc/all.js". 24021 * Note that the normalization data for practically all script automatically depend 24022 * on data for the Common script (code "Zyyy") which contains all of the characters 24023 * that are commonly used in many different scripts. Examples of characters in the 24024 * Common script are the ASCII punctuation characters, or the ASCII Arabic 24025 * numerals "0" through "9".<p> 24026 * 24027 * By default, none of the data for normalization is automatically 24028 * included in the preassembled iliball.js file. 24029 * If you would like to normalize strings, you must assemble 24030 * your own copy of ilib and explicitly include the normalization data 24031 * for those scripts as per the instructions above. This normalization method will 24032 * produce output, even without the normalization data. However, the output will be 24033 * simply the same thing as its input for all scripts 24034 * except Korean Hangul and Jamo, which are decomposed and recomposed 24035 * algorithmically and therefore do not rely on data.<p> 24036 * 24037 * If characters are encountered for which there are no normalization data, they 24038 * will be passed through to the output string unmodified. 24039 * 24040 * @param {string} form The normalization form requested 24041 * @return {IString} a new instance of an IString that has been normalized 24042 * according to the requested form. The current instance is not modified. 24043 */ 24044 NormString.prototype.normalize = function (form) { 24045 var i; 24046 24047 if (typeof(form) !== 'string' || this.str.length === 0) { 24048 return new IString(this.str); 24049 } 24050 24051 var nfc = false, 24052 nfkd = false; 24053 24054 switch (form) { 24055 default: 24056 break; 24057 24058 case "nfc": 24059 nfc = true; 24060 break; 24061 24062 case "nfkd": 24063 nfkd = true; 24064 break; 24065 24066 case "nfkc": 24067 nfkd = true; 24068 nfc = true; 24069 break; 24070 } 24071 24072 // decompose 24073 var ch, it, decomp = ""; 24074 24075 if (nfkd) { 24076 it = IString.prototype.charIterator.call(this); 24077 while (it.hasNext()) { 24078 ch = it.next(); 24079 decomp += NormString._expand(ch, ilib.data.norm.nfd, ilib.data.norm.nfkd); 24080 } 24081 } else { 24082 it = IString.prototype.charIterator.call(this); 24083 while (it.hasNext()) { 24084 ch = it.next(); 24085 decomp += NormString._expand(ch, ilib.data.norm.nfd); 24086 } 24087 } 24088 24089 // now put the combining marks in a fixed order by 24090 // sorting on the combining class 24091 function compareByCCC(left, right) { 24092 return ilib.data.norm.ccc[left] - ilib.data.norm.ccc[right]; 24093 } 24094 24095 function ccc(c) { 24096 return ilib.data.norm.ccc[c] || 0; 24097 } 24098 24099 function sortChars(arr, comp) { 24100 // qt/qml's Javascript engine re-arranges entries that are equal to 24101 // each other. Technically, that is a correct behaviour, but it is 24102 // not desirable. All the other engines leave equivalent entries 24103 // where they are. This bubblesort emulates what the other engines 24104 // do. Fortunately, the arrays we are sorting are a max of 5 or 6 24105 // entries, so performance is not a big deal here. 24106 if (ilib._getPlatform() === "qt") { 24107 var tmp; 24108 for (var i = arr.length-1; i > 0; i--) { 24109 for (var j = 0; j < i; j++) { 24110 if (comp(arr[j], arr[j+1]) > 0) { 24111 tmp = arr[j]; 24112 arr[j] = arr[j+1]; 24113 arr[j+1] = tmp; 24114 } 24115 } 24116 } 24117 return arr; 24118 } else { 24119 return arr.sort(comp); 24120 } 24121 } 24122 24123 var dstr = new IString(decomp); 24124 it = dstr.charIterator(); 24125 var end, testChar, cpArray = []; 24126 24127 // easier to deal with as an array of chars 24128 while (it.hasNext()) { 24129 cpArray.push(it.next()); 24130 } 24131 24132 i = 0; 24133 while (i < cpArray.length) { 24134 if (typeof(ilib.data.norm.ccc[cpArray[i]]) !== 'undefined' && ccc(cpArray[i]) !== 0) { 24135 // found a non-starter... rearrange all the non-starters until the next starter 24136 end = i+1; 24137 while (end < cpArray.length && 24138 typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 24139 ccc(cpArray[end]) !== 0) { 24140 end++; 24141 } 24142 24143 // simple sort of the non-starter chars 24144 if (end - i > 1) { 24145 cpArray = cpArray.slice(0,i).concat(sortChars(cpArray.slice(i, end), compareByCCC), cpArray.slice(end)); 24146 } 24147 } 24148 i++; 24149 } 24150 24151 if (nfc) { 24152 i = 0; 24153 while (i < cpArray.length) { 24154 if (typeof(ilib.data.norm.ccc[cpArray[i]]) === 'undefined' || ilib.data.norm.ccc[cpArray[i]] === 0) { 24155 // found a starter... find all the non-starters until the next starter. Must include 24156 // the next starter because under some odd circumstances, two starters sometimes recompose 24157 // together to form another character 24158 end = i+1; 24159 var notdone = true; 24160 while (end < cpArray.length && notdone) { 24161 if (typeof(ilib.data.norm.ccc[cpArray[end]]) !== 'undefined' && 24162 ilib.data.norm.ccc[cpArray[end]] !== 0) { 24163 if (ccc(cpArray[end-1]) < ccc(cpArray[end])) { 24164 // not blocked 24165 testChar = GlyphString._compose(cpArray[i], cpArray[end]); 24166 if (typeof(testChar) !== 'undefined') { 24167 cpArray[i] = testChar; 24168 24169 // delete the combining char 24170 cpArray.splice(end,1); 24171 24172 // restart the iteration, just in case there is more to recompose with the new char 24173 end = i; 24174 } 24175 } 24176 end++; 24177 } else { 24178 // found the next starter. See if this can be composed with the previous starter 24179 testChar = GlyphString._compose(cpArray[i], cpArray[end]); 24180 if (ccc(cpArray[end-1]) === 0 && typeof(testChar) !== 'undefined') { 24181 // not blocked and there is a mapping 24182 cpArray[i] = testChar; 24183 24184 // delete the combining char 24185 cpArray.splice(end,1); 24186 24187 // restart the iteration, just in case there is more to recompose with the new char 24188 end = i+1; 24189 } else { 24190 // finished iterating 24191 notdone = false; 24192 } 24193 } 24194 } 24195 } 24196 i++; 24197 } 24198 } 24199 24200 return new IString(cpArray.length > 0 ? cpArray.join("") : ""); 24201 }; 24202 24203 /** 24204 * @override 24205 * Return an iterator that will step through all of the characters 24206 * in the string one at a time, taking care to step through decomposed 24207 * characters and through surrogate pairs in UTF-16 encoding 24208 * properly. <p> 24209 * 24210 * The NormString class will return decomposed Unicode characters 24211 * as a single unit that a user might see on the screen. If the 24212 * next character in the iteration is a base character and it is 24213 * followed by combining characters, the base and all its following 24214 * combining characters are returned as a single unit.<p> 24215 * 24216 * The standard Javascript String's charAt() method only 24217 * returns information about a particular 16-bit character in the 24218 * UTF-16 encoding scheme. 24219 * If the index is pointing to a low- or high-surrogate character, 24220 * it will return that surrogate character rather 24221 * than the surrogate pair which represents a character 24222 * in the supplementary planes.<p> 24223 * 24224 * The iterator instance returned has two methods, hasNext() which 24225 * returns true if the iterator has more characters to iterate through, 24226 * and next() which returns the next character.<p> 24227 * 24228 * @return {Object} an iterator 24229 * that iterates through all the characters in the string 24230 */ 24231 NormString.prototype.charIterator = function() { 24232 var it = IString.prototype.charIterator.call(this); 24233 24234 /** 24235 * @constructor 24236 */ 24237 function _chiterator (istring) { 24238 /** 24239 * @private 24240 */ 24241 var ccc = function(c) { 24242 return ilib.data.norm.ccc[c] || 0; 24243 }; 24244 24245 this.index = 0; 24246 this.hasNext = function () { 24247 return !!this.nextChar || it.hasNext(); 24248 }; 24249 this.next = function () { 24250 var ch = this.nextChar || it.next(), 24251 prevCcc = ccc(ch), 24252 nextCcc, 24253 composed = ch; 24254 24255 this.nextChar = undefined; 24256 24257 if (ilib.data.norm.ccc && 24258 (typeof(ilib.data.norm.ccc[ch]) === 'undefined' || ccc(ch) === 0)) { 24259 // found a starter... find all the non-starters until the next starter. Must include 24260 // the next starter because under some odd circumstances, two starters sometimes recompose 24261 // together to form another character 24262 var notdone = true; 24263 while (it.hasNext() && notdone) { 24264 this.nextChar = it.next(); 24265 nextCcc = ccc(this.nextChar); 24266 if (typeof(ilib.data.norm.ccc[this.nextChar]) !== 'undefined' && nextCcc !== 0) { 24267 ch += this.nextChar; 24268 this.nextChar = undefined; 24269 } else { 24270 // found the next starter. See if this can be composed with the previous starter 24271 var testChar = GlyphString._compose(composed, this.nextChar); 24272 if (prevCcc === 0 && typeof(testChar) !== 'undefined') { 24273 // not blocked and there is a mapping 24274 composed = testChar; 24275 ch += this.nextChar; 24276 this.nextChar = undefined; 24277 } else { 24278 // finished iterating, leave this.nextChar for the next next() call 24279 notdone = false; 24280 } 24281 } 24282 prevCcc = nextCcc; 24283 } 24284 } 24285 return ch; 24286 }; 24287 }; 24288 return new _chiterator(this); 24289 }; 24290 24291 24292 24293 /*< CodePointSource.js */ 24294 /* 24295 * CodePointSource.js - Source of code points from a string 24296 * 24297 * Copyright © 2013-2015, JEDLSoft 24298 * 24299 * Licensed under the Apache License, Version 2.0 (the "License"); 24300 * you may not use this file except in compliance with the License. 24301 * You may obtain a copy of the License at 24302 * 24303 * http://www.apache.org/licenses/LICENSE-2.0 24304 * 24305 * Unless required by applicable law or agreed to in writing, software 24306 * distributed under the License is distributed on an "AS IS" BASIS, 24307 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24308 * 24309 * See the License for the specific language governing permissions and 24310 * limitations under the License. 24311 */ 24312 24313 // !depends isPunct.js NormString.js 24314 24315 24316 /** 24317 * @class 24318 * Represents a buffered source of code points. The input string is first 24319 * normalized so that combining characters come out in a standardized order. 24320 * If the "ignorePunctuation" flag is turned on, then punctuation 24321 * characters are skipped. 24322 * 24323 * @constructor 24324 * @private 24325 * @param {NormString|string} str a string to get code points from 24326 * @param {boolean} ignorePunctuation whether or not to ignore punctuation 24327 * characters 24328 */ 24329 var CodePointSource = function(str, ignorePunctuation) { 24330 this.chars = []; 24331 // first convert the string to a normalized sequence of characters 24332 var s = (typeof(str) === "string") ? new NormString(str) : str; 24333 this.it = s.charIterator(); 24334 this.ignorePunctuation = typeof(ignorePunctuation) === "boolean" && ignorePunctuation; 24335 }; 24336 24337 /** 24338 * Return the first num code points in the source without advancing the 24339 * source pointer. If there are not enough code points left in the 24340 * string to satisfy the request, this method will return undefined. 24341 * 24342 * @param {number} num the number of characters to peek ahead 24343 * @return {string|undefined} a string formed out of up to num code points from 24344 * the start of the string, or undefined if there are not enough character left 24345 * in the source to complete the request 24346 */ 24347 CodePointSource.prototype.peek = function(num) { 24348 if (num < 1) { 24349 return undefined; 24350 } 24351 if (this.chars.length < num && this.it.hasNext()) { 24352 for (var i = 0; this.chars.length < 4 && this.it.hasNext(); i++) { 24353 var c = this.it.next(); 24354 if (c && !this.ignorePunctuation || !isPunct(c)) { 24355 this.chars.push(c); 24356 } 24357 } 24358 } 24359 if (this.chars.length < num) { 24360 return undefined; 24361 } 24362 return this.chars.slice(0, num).join(""); 24363 }; 24364 /** 24365 * Advance the source pointer by the given number of code points. 24366 * @param {number} num number of code points to advance 24367 */ 24368 CodePointSource.prototype.consume = function(num) { 24369 if (num > 0) { 24370 this.peek(num); // for the iterator to go forward if needed 24371 if (num < this.chars.length) { 24372 this.chars = this.chars.slice(num); 24373 } else { 24374 this.chars = []; 24375 } 24376 } 24377 }; 24378 24379 24380 24381 /*< ElementIterator.js */ 24382 /* 24383 * ElementIterator.js - Iterate through a list of collation elements 24384 * 24385 * Copyright © 2013-2015, JEDLSoft 24386 * 24387 * Licensed under the Apache License, Version 2.0 (the "License"); 24388 * you may not use this file except in compliance with the License. 24389 * You may obtain a copy of the License at 24390 * 24391 * http://www.apache.org/licenses/LICENSE-2.0 24392 * 24393 * Unless required by applicable law or agreed to in writing, software 24394 * distributed under the License is distributed on an "AS IS" BASIS, 24395 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24396 * 24397 * See the License for the specific language governing permissions and 24398 * limitations under the License. 24399 */ 24400 24401 /** 24402 * @class 24403 * An iterator through a sequence of collation elements. This 24404 * iterator takes a source of code points, converts them into 24405 * collation elements, and allows the caller to get single 24406 * elements at a time. 24407 * 24408 * @constructor 24409 * @private 24410 * @param {CodePointSource} source source of code points to 24411 * convert to collation elements 24412 * @param {Object} map mapping from sequences of code points to 24413 * collation elements 24414 * @param {number} keysize size in bits of the collation elements 24415 */ 24416 var ElementIterator = function (source, map, keysize) { 24417 this.elements = []; 24418 this.source = source; 24419 this.map = map; 24420 this.keysize = keysize; 24421 }; 24422 24423 /** 24424 * @private 24425 */ 24426 ElementIterator.prototype._fillBuffer = function () { 24427 var str = undefined; 24428 24429 // peek ahead by up to 4 characters, which may combine 24430 // into 1 or more collation elements 24431 for (var i = 4; i > 0; i--) { 24432 str = this.source.peek(i); 24433 if (str && this.map[str]) { 24434 this.elements = this.elements.concat(this.map[str]); 24435 this.source.consume(i); 24436 return; 24437 } 24438 } 24439 24440 if (str) { 24441 // no mappings for the first code point, so just use its 24442 // Unicode code point as a proxy for its sort order. Shift 24443 // it by the key size so that everything unknown sorts 24444 // after things that have mappings 24445 this.elements.push(str.charCodeAt(0) << this.keysize); 24446 this.source.consume(1); 24447 } else { 24448 // end of the string 24449 return undefined; 24450 } 24451 }; 24452 24453 /** 24454 * Return true if there are more collation elements left to 24455 * iterate through. 24456 * @returns {boolean} true if there are more elements left to 24457 * iterate through, and false otherwise 24458 */ 24459 ElementIterator.prototype.hasNext = function () { 24460 if (this.elements.length < 1) { 24461 this._fillBuffer(); 24462 } 24463 return !!this.elements.length; 24464 }; 24465 24466 /** 24467 * Return the next collation element. If more than one collation 24468 * element is generated from a sequence of code points 24469 * (ie. an "expansion"), then this class will buffer the 24470 * other elements and return them on subsequent calls to 24471 * this method. 24472 * 24473 * @returns {number|undefined} the next collation element or 24474 * undefined for no more collation elements 24475 */ 24476 ElementIterator.prototype.next = function () { 24477 if (this.elements.length < 1) { 24478 this._fillBuffer(); 24479 } 24480 var ret = this.elements[0]; 24481 this.elements = this.elements.slice(1); 24482 return ret; 24483 }; 24484 24485 24486 24487 /*< Collator.js */ 24488 /* 24489 * Collator.js - Collation routines 24490 * 24491 * Copyright © 2013-2015, JEDLSoft 24492 * 24493 * Licensed under the Apache License, Version 2.0 (the "License"); 24494 * you may not use this file except in compliance with the License. 24495 * You may obtain a copy of the License at 24496 * 24497 * http://www.apache.org/licenses/LICENSE-2.0 24498 * 24499 * Unless required by applicable law or agreed to in writing, software 24500 * distributed under the License is distributed on an "AS IS" BASIS, 24501 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 24502 * 24503 * See the License for the specific language governing permissions and 24504 * limitations under the License. 24505 */ 24506 24507 /* !depends 24508 Locale.js 24509 ilib.js 24510 INumber.js 24511 isPunct.js 24512 NormString.js 24513 Utils.js 24514 JSUtils.js 24515 CodePointSource.js 24516 ElementIterator.js 24517 GlyphString.js 24518 */ 24519 24520 // !data collation 24521 24522 24523 /** 24524 * @class 24525 * A class that implements a locale-sensitive comparator function 24526 * for use with sorting function. The comparator function 24527 * assumes that the strings it is comparing contain Unicode characters 24528 * encoded in UTF-16.<p> 24529 * 24530 * Collations usually depend only on the language, because most collation orders 24531 * are shared between locales that speak the same language. There are, however, a 24532 * number of instances where a locale collates differently than other locales 24533 * that share the same language. There are also a number of instances where a 24534 * locale collates differently based on the script used. This object can handle 24535 * these cases automatically if a full locale is specified in the options rather 24536 * than just a language code.<p> 24537 * 24538 * <h2>Options</h2> 24539 * 24540 * The options parameter can contain any of the following properties: 24541 * 24542 * <ul> 24543 * <li><i>locale</i> - String|Locale. The locale which the comparator function 24544 * will collate with. Default: the current iLib locale. 24545 * 24546 * <li><i>sensitivity</i> - String. Sensitivity or strength of collator. This is one of 24547 * "primary", "base", "secondary", "accent", "tertiary", "case", "quaternary", or 24548 * "variant". Default: "primary" 24549 * <ol> 24550 * <li>base or primary - Only the primary distinctions between characters are significant. 24551 * Another way of saying that is that the collator will be case-, accent-, and 24552 * variation-insensitive, and only distinguish between the base characters 24553 * <li>case or secondary - Both the primary and secondary distinctions between characters 24554 * are significant. That is, the collator will be accent- and variation-insensitive 24555 * and will distinguish between base characters and character case. 24556 * <li>accent or tertiary - The primary, secondary, and tertiary distinctions between 24557 * characters are all significant. That is, the collator will be 24558 * variation-insensitive, but accent-, case-, and base-character-sensitive. 24559 * <li>variant or quaternary - All distinctions between characters are significant. That is, 24560 * the algorithm is base character-, case-, accent-, and variation-sensitive. 24561 * </ol> 24562 * 24563 * <li><i>upperFirst</i> - boolean. When collating case-sensitively in a script that 24564 * has the concept of case, put upper-case 24565 * characters first, otherwise lower-case will come first. Warning: some browsers do 24566 * not implement this feature or at least do not implement it properly, so if you are 24567 * using the native collator with this option, you may get different results in different 24568 * browsers. To guarantee the same results, set useNative to false to use the ilib 24569 * collator implementation. This of course will be somewhat slower, but more 24570 * predictable. Default: true 24571 * 24572 * <li><i>reverse</i> - boolean. Return the list sorted in reverse order. When the 24573 * upperFirst option is also set to true, upper-case characters would then come at 24574 * the end of the list. Default: false. 24575 * 24576 * <li><i>scriptOrder</i> - string. When collating strings in multiple scripts, 24577 * this property specifies what order those scripts should be sorted. The default 24578 * Unicode Collation Algorithm (UCA) already has a default order for scripts, but 24579 * this can be tailored via this property. The value of this option is a 24580 * space-separated list of ISO 15924 scripts codes. If a code is specified in this 24581 * property, its default data must be included using the JS assembly tool. If the 24582 * data is not included, the ordering for the script will be ignored. Default: 24583 * the default order defined by the UCA. 24584 * 24585 * <li><i>style</i> - The value of the style parameter is dependent on the locale. 24586 * For some locales, there are different styles of collating strings depending 24587 * on what kind of strings are being collated or what the preference of the user 24588 * is. For example, in German, there is a phonebook order and a dictionary ordering 24589 * that sort the same array of strings slightly differently. 24590 * The static method {@link Collator#getAvailableStyles} will return a list of styles that ilib 24591 * currently knows about for any given locale. If the value of the style option is 24592 * not recognized for a locale, it will be ignored. Default style is "standard".<p> 24593 * 24594 * <li><i>usage</i> - Whether this collator will be used for searching or sorting. 24595 * Valid values are simply the strings "sort" or "search". When used for sorting, 24596 * it is good idea if a collator produces a stable sort. That is, the order of the 24597 * sorted array of strings should not depend on the order of the strings in the 24598 * input array. As such, when a collator is supposed to act case insensitively, 24599 * it nonetheless still distinguishes between case after all other criteria 24600 * are satisfied so that strings that are distinguished only by case do not sort 24601 * randomly. For searching, we would like to match two strings that different only 24602 * by case, so the collator must return equals in that situation instead of 24603 * further distinguishing by case. Default is "sort". 24604 * 24605 * <li><i>numeric</i> - Treat the left and right strings as if they started with 24606 * numbers and sort them numerically rather than lexically. 24607 * 24608 * <li><i>ignorePunctuation</i> - Skip punctuation characters when comparing the 24609 * strings. 24610 * 24611 * <li>onLoad - a callback function to call when the collator object is fully 24612 * loaded. When the onLoad option is given, the collator object will attempt to 24613 * load any missing locale data using the ilib loader callback. 24614 * When the constructor is done (even if the data is already preassembled), the 24615 * onLoad function is called with the current instance as a parameter, so this 24616 * callback can be used with preassembled or dynamic loading or a mix of the two. 24617 * 24618 * <li>sync - tell whether to load any missing locale data synchronously or 24619 * asynchronously. If this option is given as "false", then the "onLoad" 24620 * callback must be given, as the instance returned from this constructor will 24621 * not be usable for a while. 24622 * 24623 * <li><i>loadParams</i> - an object containing parameters to pass to the 24624 * loader callback function when locale data is missing. The parameters are not 24625 * interpretted or modified in any way. They are simply passed along. The object 24626 * may contain any property/value pairs as long as the calling code is in 24627 * agreement with the loader callback function as to what those parameters mean. 24628 * 24629 * <li><i>useNative</i> - when this option is true, use the native Intl object 24630 * provided by the Javascript engine, if it exists, to implement this class. If 24631 * it doesn't exist, or if this parameter is false, then this class uses a pure 24632 * Javascript implementation, which is slower and uses a lot more memory, but 24633 * works everywhere that ilib works. Default is "true". 24634 * </ul> 24635 * 24636 * <h2>Operation</h2> 24637 * 24638 * The Collator constructor returns a collator object tailored with the above 24639 * options. The object contains an internal compare() method which compares two 24640 * strings according to those options. This can be used directly to compare 24641 * two strings, but is not useful for passing to the javascript sort function 24642 * because then it will not have its collation data available. Instead, use the 24643 * getComparator() method to retrieve a function that is bound to the collator 24644 * object. (You could also bind it yourself using ilib.bind()). The bound function 24645 * can be used with the standard Javascript array sorting algorithm, or as a 24646 * comparator with your own sorting algorithm.<p> 24647 * 24648 * Example using the standard Javascript array sorting call with the bound 24649 * function:<p> 24650 * 24651 * <code> 24652 * <pre> 24653 * var arr = ["ö", "oe", "ü", "o", "a", "ae", "u", "ß", "ä"]; 24654 * var collator = new Collator({locale: 'de-DE', style: "dictionary"}); 24655 * arr.sort(collator.getComparator()); 24656 * console.log(JSON.stringify(arr)); 24657 * </pre> 24658 * </code> 24659 * <p> 24660 * 24661 * Would give the output:<p> 24662 * 24663 * <code> 24664 * <pre> 24665 * ["a", "ae", "ä", "o", "oe", "ö", "ß", "u", "ü"] 24666 * </pre> 24667 * </code> 24668 * 24669 * When sorting an array of Javascript objects according to one of the 24670 * string properties of the objects, wrap the collator's compare function 24671 * in your own comparator function that knows the structure of the objects 24672 * being sorted:<p> 24673 * 24674 * <code> 24675 * <pre> 24676 * var collator = new Collator({locale: 'de-DE'}); 24677 * var myComparator = function (collator) { 24678 * var comparator = collator.getComparator(); 24679 * // left and right are your own objects 24680 * return function (left, right) { 24681 * return comparator(left.x.y.textProperty, right.x.y.textProperty); 24682 * }; 24683 * }; 24684 * arr.sort(myComparator(collator)); 24685 * </pre> 24686 * </code> 24687 * <p> 24688 * 24689 * <h2>Sort Keys</h2> 24690 * 24691 * The collator class also has a method to retrieve the sort key for a 24692 * string. The sort key is an array of values that represent how each 24693 * character in the string should be collated according to the characteristics 24694 * of the collation algorithm and the given options. Thus, sort keys can be 24695 * compared directly value-for-value with other sort keys that were generated 24696 * by the same collator, and the resulting ordering is guaranteed to be the 24697 * same as if the original strings were compared by the collator. 24698 * Sort keys generated by different collators are not guaranteed to give 24699 * any reasonable results when compared together unless the two collators 24700 * were constructed with 24701 * exactly the same options and therefore end up representing the exact same 24702 * collation sequence.<p> 24703 * 24704 * A good rule of thumb is that you would use a sort key if you had 10 or more 24705 * items to sort or if your array might be resorted arbitrarily. For example, if your 24706 * user interface was displaying a table with 100 rows in it, and each row had 24707 * 4 sortable text columns which could be sorted in acending or descending order, 24708 * the recommended practice would be to generate a sort key for each of the 4 24709 * sortable fields in each row and store that in the Javascript representation of the 24710 * table data. Then, when the user clicks on a column header to resort the 24711 * table according to that column, the resorting would be relatively quick 24712 * because it would only be comparing arrays of values, and not recalculating 24713 * the collation values for each character in each string for every comparison.<p> 24714 * 24715 * For tables that are large, it is usually a better idea to do the sorting 24716 * on the server side, especially if the table is the result of a database 24717 * query. In this case, the table is usually a view of the cursor of a large 24718 * results set, and only a few entries are sent to the front end at a time. 24719 * In order to sort the set efficiently, it should be done on the database 24720 * level instead. 24721 * 24722 * <h2>Data</h2> 24723 * 24724 * Doing correct collation entails a huge amount of mapping data, much of which is 24725 * not necessary when collating in one language with one script, which is the most 24726 * common case. Thus, ilib implements a number of ways to include the data you 24727 * need or leave out the data you don't need using the JS assembly tool: 24728 * 24729 * <ol> 24730 * <li>Full multilingual data - if you are sorting multilingual data and need to collate 24731 * text written in multiple scripts, you can use the directive "!data collation/ducet" to 24732 * load in the full collation data. This allows the collator to perform the entire 24733 * Unicode Collation Algorithm (UCA) based on the Default Unicode Collation Element 24734 * Table (DUCET). The data is very large, on the order of multiple megabytes, but 24735 * sometimes it is necessary. 24736 * <li>A few scripts - if you are sorting text written in only a few scripts, you may 24737 * want to include only the data for those scripts. Each ISO 15924 script code has its 24738 * own data available in a separate file, so you can use the data directive to include 24739 * only the data for the scripts you need. For example, use 24740 * "!data collation/Latn" to retrieve the collation information for the Latin script. 24741 * Because the "ducet" table mentioned in the previous point is a superset of the 24742 * tables for all other scripts, you do not need to include explicitly the data for 24743 * any particular script when using "ducet". That is, you either include "ducet" or 24744 * you include a specific list of scripts. 24745 * <li>Only one script - if you are sorting text written only in one script, you can 24746 * either include the data directly as in the previous point, or you can rely on the 24747 * locale to include the correct data for you. In this case, you can use the directive 24748 * "!data collate" to load in the locale's collation data for its most common script. 24749 * </ol> 24750 * 24751 * With any of the above ways of including the data, the collator will only perform the 24752 * correct language-sensitive sorting for the given locale. All other scripts will be 24753 * sorted in the default manner according to the UCA. For example, if you include the 24754 * "ducet" data and pass in "de-DE" (German for Germany) as the locale spec, then 24755 * only the Latin script (the default script for German) will be sorted according to 24756 * German rules. All other scripts in the DUCET, such as Japanese or Arabic, will use 24757 * the default UCA collation rules.<p> 24758 * 24759 * If this collator encounters a character for which it has no collation data, it will 24760 * sort those characters by pure Unicode value after all characters for which it does have 24761 * collation data. For example, if you only loaded in the German collation data (ie. the 24762 * data for the Latin script tailored to German) to sort a list of person names, but that 24763 * list happens to include the names of a few Japanese people written in Japanese 24764 * characters, the Japanese names will sort at the end of the list after all German names, 24765 * and will sort according to the Unicode values of the characters. 24766 * 24767 * @constructor 24768 * @param {Object} options options governing how the resulting comparator 24769 * function will operate 24770 */ 24771 var Collator = function(options) { 24772 var sync = true, 24773 loadParams = undefined, 24774 useNative = true; 24775 24776 // defaults 24777 /** 24778 * @private 24779 * @type {Locale} 24780 */ 24781 this.locale = new Locale(ilib.getLocale()); 24782 24783 /** @private */ 24784 this.caseFirst = "upper"; 24785 /** @private */ 24786 this.sensitivity = "variant"; 24787 /** @private */ 24788 this.level = 4; 24789 /** @private */ 24790 this.usage = "sort"; 24791 /** @private */ 24792 this.reverse = false; 24793 /** @private */ 24794 this.numeric = false; 24795 /** @private */ 24796 this.style = "default"; 24797 /** @private */ 24798 this.ignorePunctuation = false; 24799 24800 if (options) { 24801 if (options.locale) { 24802 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 24803 } 24804 if (options.sensitivity) { 24805 switch (options.sensitivity) { 24806 case 'primary': 24807 case 'base': 24808 this.sensitivity = "base"; 24809 this.level = 1; 24810 break; 24811 case 'secondary': 24812 case 'accent': 24813 this.sensitivity = "accent"; 24814 this.level = 2; 24815 break; 24816 case 'tertiary': 24817 case 'case': 24818 this.sensitivity = "case"; 24819 this.level = 3; 24820 break; 24821 case 'quaternary': 24822 case 'variant': 24823 this.sensitivity = "variant"; 24824 this.level = 4; 24825 break; 24826 } 24827 } 24828 if (typeof(options.upperFirst) !== 'undefined') { 24829 this.caseFirst = options.upperFirst ? "upper" : "lower"; 24830 } 24831 24832 if (typeof(options.ignorePunctuation) !== 'undefined') { 24833 this.ignorePunctuation = options.ignorePunctuation; 24834 } 24835 if (typeof(options.sync) !== 'undefined') { 24836 sync = !!options.sync; 24837 } 24838 24839 loadParams = options.loadParams; 24840 if (typeof(options.useNative) !== 'undefined') { 24841 useNative = options.useNative; 24842 } 24843 24844 if (options.usage === "sort" || options.usage === "search") { 24845 this.usage = options.usage; 24846 } 24847 24848 if (typeof(options.reverse) === 'boolean') { 24849 this.reverse = options.reverse; 24850 } 24851 24852 if (typeof(options.numeric) === 'boolean') { 24853 this.numeric = options.numeric; 24854 } 24855 24856 if (typeof(options.style) === 'string') { 24857 this.style = options.style; 24858 } 24859 } else { 24860 options = {sync: true}; 24861 } 24862 24863 if (this.usage === "sort") { 24864 // produces a stable sort 24865 this.level = 4; 24866 } 24867 24868 if (useNative && typeof(Intl) !== 'undefined' && Intl) { 24869 // this engine is modern and supports the new Intl object! 24870 //console.log("implemented natively"); 24871 /** 24872 * @private 24873 * @type {{compare:function(string,string)}} 24874 */ 24875 this.collator = new Intl.Collator(this.locale.getSpec(), { 24876 sensitivity: this.sensitivity, 24877 caseFirst: this.caseFirst, 24878 ignorePunctuation: this.ignorePunctuation, 24879 numeric: this.numeric, 24880 usage: this.usage 24881 }); 24882 24883 if (options && typeof(options.onLoad) === 'function') { 24884 options.onLoad(this); 24885 } 24886 } else { 24887 //console.log("implemented in pure JS"); 24888 24889 // else implement in pure Javascript 24890 Utils.loadData({ 24891 object: "Collator", 24892 locale: this.locale, 24893 name: "collation.json", 24894 sync: sync, 24895 loadParams: loadParams, 24896 callback: ilib.bind(this, function (collation) { 24897 if (!collation) { 24898 collation = ilib.data.collation; 24899 var spec = this.locale.getSpec().replace(/-/g, '_'); 24900 ilib.data.cache.Collator[spec] = collation; 24901 } 24902 this._initCollation(collation); 24903 if (this.ignorePunctuation) { 24904 isPunct._init(sync, loadParams, ilib.bind(this, function() { 24905 this._init(options); 24906 })); 24907 } else { 24908 this._init(options); 24909 } 24910 }) 24911 }); 24912 } 24913 }; 24914 24915 Collator.prototype = { 24916 /** 24917 * @private 24918 */ 24919 _init: function(options) { 24920 if (this.numeric) { 24921 // Create a fake INumber instance now to guarantee that the locale data 24922 // is loaded so we can create sync INumber instances later, even in async mode 24923 new INumber("1", { 24924 sync: options.sync, 24925 loadParams: options.loadParams, 24926 onLoad: function(n) { 24927 if (typeof(options.onLoad) === 'function') { 24928 options.onLoad(this); 24929 } 24930 } 24931 }) 24932 } else { 24933 if (typeof(options.onLoad) === 'function') { 24934 options.onLoad(this); 24935 } 24936 } 24937 }, 24938 24939 /** 24940 * @private 24941 * Bit pack an array of values into a single number 24942 * @param {number|null|Array.<number>} arr array of values to bit pack 24943 * @param {number} offset offset for the start of this map 24944 */ 24945 _pack: function (arr, offset) { 24946 var value = 0; 24947 if (arr) { 24948 if (typeof(arr) === 'number') { 24949 arr = [ arr ]; 24950 } 24951 for (var i = 0; i < this.level; i++) { 24952 var thisLevel = (typeof(arr[i]) !== "undefined" ? arr[i] : 0); 24953 if (i === 0) { 24954 thisLevel += offset; 24955 } 24956 if (i > 0) { 24957 value <<= this.collation.bits[i]; 24958 } 24959 if (i === 2 && this.caseFirst === "lower") { 24960 // sort the lower case first instead of upper 24961 value = value | (1 - thisLevel); 24962 } else { 24963 value = value | thisLevel; 24964 } 24965 } 24966 } 24967 return value; 24968 }, 24969 24970 /** 24971 * @private 24972 * Return the rule packed into an array of collation elements. 24973 * @param {Array.<number|null|Array.<number>>} rule 24974 * @param {number} offset 24975 * @return {Array.<number>} a bit-packed array of numbers 24976 */ 24977 _packRule: function(rule, offset) { 24978 if (ilib.isArray(rule[0])) { 24979 var ret = []; 24980 for (var i = 0; i < rule.length; i++) { 24981 ret.push(this._pack(rule[i], offset)); 24982 } 24983 return ret; 24984 } else { 24985 return [ this._pack(rule, offset) ]; 24986 } 24987 }, 24988 24989 /** 24990 * @private 24991 */ 24992 _addChars: function (str, offset) { 24993 var gs = new GlyphString(str); 24994 var it = gs.charIterator(); 24995 var c; 24996 24997 while (it.hasNext()) { 24998 c = it.next(); 24999 if (c === "'") { 25000 // escape a sequence of chars as one collation element 25001 c = ""; 25002 var x = ""; 25003 while (it.hasNext() && x !== "'") { 25004 c += x; 25005 x = it.next(); 25006 } 25007 } 25008 this.lastMap++; 25009 this.map[c] = this._packRule([this.lastMap], offset); 25010 } 25011 }, 25012 25013 /** 25014 * @private 25015 */ 25016 _addRules: function(rules, start) { 25017 var p; 25018 for (var r in rules.map) { 25019 if (r) { 25020 this.map[r] = this._packRule(rules.map[r], start); 25021 p = typeof(rules.map[r][0]) === 'number' ? rules.map[r][0] : rules.map[r][0][0]; 25022 this.lastMap = Math.max(p + start, this.lastMap); 25023 } 25024 } 25025 25026 if (typeof(rules.ranges) !== 'undefined') { 25027 // for each range, everything in the range goes in primary sequence from the start 25028 for (var i = 0; i < rules.ranges.length; i++) { 25029 var range = rules.ranges[i]; 25030 25031 this.lastMap = range.start; 25032 if (typeof(range.chars) === "string") { 25033 this._addChars(range.chars, start); 25034 } else { 25035 for (var k = 0; k < range.chars.length; k++) { 25036 this._addChars(range.chars[k], start); 25037 } 25038 } 25039 } 25040 } 25041 }, 25042 25043 /** 25044 * @private 25045 */ 25046 _initCollation: function(rules) { 25047 var rule = this.style; 25048 while (typeof(rule) === 'string') { 25049 rule = rules[rule]; 25050 } 25051 25052 if (!rule) { 25053 rule = "default"; 25054 25055 while (typeof(rule) === 'string') { 25056 rule = rules[rule]; 25057 } 25058 } 25059 if (!rule) { 25060 this.map = {}; 25061 return; 25062 } 25063 25064 /** 25065 * @private 25066 * @type {{scripts:Array.<string>,bits:Array.<number>,maxes:Array.<number>,bases:Array.<number>,map:Object.<string,Array.<number|null|Array.<number>>>}} 25067 */ 25068 this.collation = rule; 25069 this.map = {}; 25070 this.lastMap = -1; 25071 this.keysize = this.collation.keysize[this.level-1]; 25072 this.defaultRule = rules["default"]; 25073 25074 if (typeof(this.collation.inherit) !== 'undefined') { 25075 for (var i = 0; i < this.collation.inherit.length; i++) { 25076 if (this.collation.inherit === 'this') { 25077 continue; 25078 } 25079 var col = this.collation.inherit[i]; 25080 rule = typeof(col) === 'object' ? col.name : col; 25081 if (rules[rule]) { 25082 this._addRules(rules[rule], col.start || this.lastMap+1); 25083 } 25084 } 25085 } 25086 this._addRules(this.collation, this.lastMap+1); 25087 }, 25088 25089 /** 25090 * @private 25091 */ 25092 _basicCompare: function(left, right) { 25093 var l = (left instanceof NormString) ? left : new NormString(left), 25094 r = (right instanceof NormString) ? right : new NormString(right), 25095 lelements, 25096 relements, 25097 diff; 25098 25099 if (this.numeric) { 25100 var lvalue = new INumber(left, {locale: this.locale}); 25101 var rvalue = new INumber(right, {locale: this.locale}); 25102 if (!isNaN(lvalue.valueOf()) && !isNaN(rvalue.valueOf())) { 25103 diff = lvalue.valueOf() - rvalue.valueOf(); 25104 if (diff) { 25105 return diff; 25106 } else { 25107 // skip the numeric part and compare the rest lexically 25108 l = new NormString(left.substring(lvalue.parsed.length)); 25109 r = new NormString(right.substring(rvalue.parsed.length)); 25110 } 25111 } 25112 // else if they aren't both numbers, then let the code below take care of the lexical comparison instead 25113 } 25114 25115 lelements = new ElementIterator(new CodePointSource(l, this.ignorePunctuation), this.map, this.keysize); 25116 relements = new ElementIterator(new CodePointSource(r, this.ignorePunctuation), this.map, this.keysize); 25117 25118 while (lelements.hasNext() && relements.hasNext()) { 25119 diff = lelements.next() - relements.next(); 25120 if (diff) { 25121 return diff; 25122 } 25123 } 25124 if (!lelements.hasNext() && !relements.hasNext()) { 25125 return 0; 25126 } else if (lelements.hasNext()) { 25127 return 1; 25128 } else { 25129 return -1; 25130 } 25131 }, 25132 25133 /** 25134 * Compare two strings together according to the rules of this 25135 * collator instance. Do not use this function directly with 25136 * Array.sort, as it will not have its collation data available 25137 * and therefore will not function properly. Use the function 25138 * returned by getComparator() instead. 25139 * 25140 * @param {string} left the left string to compare 25141 * @param {string} right the right string to compare 25142 * @return {number} a negative number if left comes before right, a 25143 * positive number if right comes before left, and zero if left and 25144 * right are equivalent according to this collator 25145 */ 25146 compare: function (left, right) { 25147 // last resort: use the "C" locale 25148 if (this.collator) { 25149 // implemented by the core engine 25150 return this.collator.compare(left, right); 25151 } 25152 25153 var ret = this._basicCompare(left, right); 25154 return this.reverse ? -ret : ret; 25155 }, 25156 25157 /** 25158 * Return a comparator function that can compare two strings together 25159 * according to the rules of this collator instance. The function 25160 * returns a negative number if the left 25161 * string comes before right, a positive number if the right string comes 25162 * before the left, and zero if left and right are equivalent. If the 25163 * reverse property was given as true to the collator constructor, this 25164 * function will 25165 * switch the sign of those values to cause sorting to happen in the 25166 * reverse order. 25167 * 25168 * @return {function(...)|undefined} a comparator function that 25169 * can compare two strings together according to the rules of this 25170 * collator instance 25171 */ 25172 getComparator: function() { 25173 // bind the function to this instance so that we have the collation 25174 // rules available to do the work 25175 if (this.collator) { 25176 // implemented by the core engine 25177 return this.collator.compare; 25178 } 25179 25180 return ilib.bind(this, this.compare); 25181 }, 25182 25183 /** 25184 * Return a sort key string for the given string. The sort key 25185 * string is a list of values that represent each character 25186 * in the original string. The sort key 25187 * values for any particular character consists of 3 numbers that 25188 * encode the primary, secondary, and tertiary characteristics 25189 * of that character. The values of each characteristic are 25190 * modified according to the strength of this collator instance 25191 * to give the correct collation order. The idea is that this 25192 * sort key string is directly comparable byte-for-byte to 25193 * other sort key strings generated by this collator without 25194 * any further knowledge of the collation rules for the locale. 25195 * More formally, if a < b according to the rules of this collation, 25196 * then it is guaranteed that sortkey(a) < sortkey(b) when compared 25197 * byte-for-byte. The sort key string can therefore be used 25198 * without the collator to sort an array of strings efficiently 25199 * because the work of determining the applicability of various 25200 * collation rules is done once up-front when generating 25201 * the sort key.<p> 25202 * 25203 * The sort key string can be treated as a regular, albeit somewhat 25204 * odd-looking, string. That is, it can be pass to regular 25205 * Javascript functions without problems. 25206 * 25207 * @param {string} str the original string to generate the sort key for 25208 * @return {string} a sort key string for the given string 25209 */ 25210 sortKey: function (str) { 25211 if (!str) { 25212 return ""; 25213 } 25214 25215 if (this.collator) { 25216 // native, no sort keys available 25217 return str; 25218 } 25219 25220 if (this.numeric) { 25221 var v = new INumber(str, {locale: this.locale}); 25222 var s = isNaN(v.valueOf()) ? "" : v.valueOf().toString(16); 25223 return JSUtils.pad(s, 16); 25224 } else { 25225 var n = (typeof(str) === "string") ? new NormString(str) : str, 25226 ret = "", 25227 lelements = new ElementIterator(new CodePointSource(n, this.ignorePunctuation), this.map, this.keysize), 25228 element; 25229 25230 while (lelements.hasNext()) { 25231 element = lelements.next(); 25232 if (this.reverse) { 25233 // for reverse, take the bitwise inverse 25234 element = (1 << this.keysize) - element; 25235 } 25236 ret += JSUtils.pad(element.toString(16), this.keysize/4); 25237 } 25238 } 25239 return ret; 25240 } 25241 }; 25242 25243 /** 25244 * Retrieve the list of collation style names that are available for the 25245 * given locale. This list varies depending on the locale, and depending 25246 * on whether or not the data for that locale was assembled into this copy 25247 * of ilib. 25248 * 25249 * @param {Locale|string=} locale The locale for which the available 25250 * styles are being sought 25251 * @return Array.<string> an array of style names that are available for 25252 * the given locale 25253 */ 25254 Collator.getAvailableStyles = function (locale) { 25255 return [ "standard" ]; 25256 }; 25257 25258 /** 25259 * Retrieve the list of ISO 15924 script codes that are available in this 25260 * copy of ilib. This list varies depending on whether or not the data for 25261 * various scripts was assembled into this copy of ilib. If the "ducet" 25262 * data is assembled into this copy of ilib, this method will report the 25263 * entire list of scripts as being available. If a collator instance is 25264 * instantiated with a script code that is not on the list returned by this 25265 * function, it will be ignored and text in that script will be sorted by 25266 * numeric Unicode values of the characters. 25267 * 25268 * @return Array.<string> an array of ISO 15924 script codes that are 25269 * available 25270 */ 25271 Collator.getAvailableScripts = function () { 25272 return [ "Latn" ]; 25273 }; 25274 25275 25276 /** 25277 * Return a default collation style 25278 * 25279 * @returns {string} default collation style such as 'latin', 'korean' etc */ 25280 Collator.prototype.getDefaultCollatorStyle = function () { 25281 return this.defaultRule; 25282 }; 25283 25284 25285 25286 /*< nfd/all.js */ 25287 /* 25288 * all.js - include file for normalization data for a particular script 25289 * 25290 * Copyright © 2013-2015, JEDLSoft 25291 * 25292 * Licensed under the Apache License, Version 2.0 (the "License"); 25293 * you may not use this file except in compliance with the License. 25294 * You may obtain a copy of the License at 25295 * 25296 * http://www.apache.org/licenses/LICENSE-2.0 25297 * 25298 * Unless required by applicable law or agreed to in writing, software 25299 * distributed under the License is distributed on an "AS IS" BASIS, 25300 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25301 * 25302 * See the License for the specific language governing permissions and 25303 * limitations under the License. 25304 */ 25305 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 25306 // !data normdata nfd/all 25307 ilib.extend(ilib.data.norm, ilib.data.normdata); 25308 ilib.extend(ilib.data.norm.nfd, ilib.data.nfd_all); 25309 ilib.data.normdata = undefined; 25310 ilib.data.nfd_all = undefined; 25311 /*< nfkd/all.js */ 25312 /* 25313 * all.js - include file for normalization data for a particular script 25314 * 25315 * Copyright © 2013-2015, JEDLSoft 25316 * 25317 * Licensed under the Apache License, Version 2.0 (the "License"); 25318 * you may not use this file except in compliance with the License. 25319 * You may obtain a copy of the License at 25320 * 25321 * http://www.apache.org/licenses/LICENSE-2.0 25322 * 25323 * Unless required by applicable law or agreed to in writing, software 25324 * distributed under the License is distributed on an "AS IS" BASIS, 25325 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25326 * 25327 * See the License for the specific language governing permissions and 25328 * limitations under the License. 25329 */ 25330 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 25331 // !depends nfd/all.js 25332 // !data normdata nfkd/all 25333 ilib.extend(ilib.data.norm, ilib.data.normdata); 25334 ilib.extend(ilib.data.norm.nfkd, ilib.data.nfkd_all); 25335 ilib.data.normdata = undefined; 25336 ilib.data.nfkd_all = undefined; 25337 /*< nfkc/all.js */ 25338 /* 25339 * all.js - include file for normalization data for a particular script 25340 * 25341 * Copyright © 2013-2015, JEDLSoft 25342 * 25343 * Licensed under the Apache License, Version 2.0 (the "License"); 25344 * you may not use this file except in compliance with the License. 25345 * You may obtain a copy of the License at 25346 * 25347 * http://www.apache.org/licenses/LICENSE-2.0 25348 * 25349 * Unless required by applicable law or agreed to in writing, software 25350 * distributed under the License is distributed on an "AS IS" BASIS, 25351 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25352 * 25353 * See the License for the specific language governing permissions and 25354 * limitations under the License. 25355 */ 25356 /* WARNING: THIS IS A FILE GENERATED BY gennorm.js. DO NOT EDIT BY HAND. */ 25357 // !depends nfd/all.js nfkd/all.js 25358 // !data norm 25359 ilib.extend(ilib.data.norm, ilib.data.normdata); 25360 ilib.data.normdata = undefined; 25361 25362 /*< LocaleMatcher.js */ 25363 /* 25364 * LocaleMatcher.js - Locale matcher definition 25365 * 25366 * Copyright © 2013-2015, JEDLSoft 25367 * 25368 * Licensed under the Apache License, Version 2.0 (the "License"); 25369 * you may not use this file except in compliance with the License. 25370 * You may obtain a copy of the License at 25371 * 25372 * http://www.apache.org/licenses/LICENSE-2.0 25373 * 25374 * Unless required by applicable law or agreed to in writing, software 25375 * distributed under the License is distributed on an "AS IS" BASIS, 25376 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25377 * 25378 * See the License for the specific language governing permissions and 25379 * limitations under the License. 25380 */ 25381 25382 // !depends ilib.js Locale.js Utils.js 25383 // !data localematch 25384 25385 25386 var componentWeights = [ 25387 0.5, // language 25388 0.2, // script 25389 0.25, // region 25390 0.05 // variant 25391 ]; 25392 25393 /** 25394 * @class 25395 * Create a new locale matcher instance. This is used 25396 * to see which locales can be matched with each other in 25397 * various ways.<p> 25398 * 25399 * The options object may contain any of the following properties: 25400 * 25401 * <ul> 25402 * <li><i>locale</i> - the locale instance or locale spec to match 25403 * 25404 * <li><i>onLoad</i> - a callback function to call when the locale matcher object is fully 25405 * loaded. When the onLoad option is given, the locale matcher object will attempt to 25406 * load any missing locale data using the ilib loader callback. 25407 * When the constructor is done (even if the data is already preassembled), the 25408 * onLoad function is called with the current instance as a parameter, so this 25409 * callback can be used with preassembled or dynamic loading or a mix of the two. 25410 * 25411 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 25412 * asynchronously. If this option is given as "false", then the "onLoad" 25413 * callback must be given, as the instance returned from this constructor will 25414 * not be usable for a while. 25415 * 25416 * <li><i>loadParams</i> - an object containing parameters to pass to the 25417 * loader callback function when locale data is missing. The parameters are not 25418 * interpretted or modified in any way. They are simply passed along. The object 25419 * may contain any property/value pairs as long as the calling code is in 25420 * agreement with the loader callback function as to what those parameters mean. 25421 * </ul> 25422 * 25423 * 25424 * @constructor 25425 * @param {Object} options parameters to initialize this matcher 25426 */ 25427 var LocaleMatcher = function(options) { 25428 var sync = true, 25429 loadParams = undefined; 25430 25431 this.locale = new Locale(); 25432 25433 if (options) { 25434 if (typeof(options.locale) !== 'undefined') { 25435 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 25436 } 25437 25438 if (typeof(options.sync) !== 'undefined') { 25439 sync = !!options.sync; 25440 } 25441 25442 if (typeof(options.loadParams) !== 'undefined') { 25443 loadParams = options.loadParams; 25444 } 25445 } 25446 25447 if (typeof(ilib.data.localematch) === 'undefined') { 25448 Utils.loadData({ 25449 object: "LocaleMatcher", 25450 locale: "-", 25451 name: "localematch.json", 25452 sync: sync, 25453 loadParams: loadParams, 25454 callback: ilib.bind(this, function (info) { 25455 if (!info) { 25456 info = {}; 25457 var spec = this.locale.getSpec().replace(/-/g, "_"); 25458 ilib.data.cache.LocaleMatcher[spec] = info; 25459 } 25460 /** @type {Object.<string,string>} */ 25461 this.info = info; 25462 if (options && typeof(options.onLoad) === 'function') { 25463 options.onLoad(this); 25464 } 25465 }) 25466 }); 25467 } else { 25468 this.info = ilib.data.localematch; 25469 if (options && typeof(options.onLoad) === 'function') { 25470 options.onLoad(this); 25471 } 25472 } 25473 }; 25474 25475 25476 LocaleMatcher.prototype = { 25477 /** 25478 * Return the locale used to construct this instance. 25479 * @return {Locale|undefined} the locale for this matcher 25480 */ 25481 getLocale: function() { 25482 return this.locale; 25483 }, 25484 25485 /** 25486 * @private 25487 * Do the work 25488 */ 25489 _getLikelyLocale: function(locale) { 25490 // already full specified 25491 if (locale.language && locale.script && locale.region) return locale; 25492 25493 if (typeof(this.info.likelyLocales[locale.getSpec()]) === 'undefined') { 25494 // try various partials before giving up 25495 var partial = this.info.likelyLocales[new Locale(locale.language, undefined, locale.region).getSpec()]; 25496 if (typeof(partial) !== 'undefined') return new Locale(partial); 25497 25498 partial = this.info.likelyLocales[new Locale(locale.language, locale.script, undefined).getSpec()]; 25499 if (typeof(partial) !== 'undefined') return new Locale(partial); 25500 25501 partial = this.info.likelyLocales[new Locale(locale.language, undefined, undefined).getSpec()]; 25502 if (typeof(partial) !== 'undefined') return new Locale(partial); 25503 25504 partial = this.info.likelyLocales[new Locale(undefined, locale.script, locale.region).getSpec()]; 25505 if (typeof(partial) !== 'undefined') return new Locale(partial); 25506 25507 partial = this.info.likelyLocales[new Locale(undefined, undefined, locale.region).getSpec()]; 25508 if (typeof(partial) !== 'undefined') return new Locale(partial); 25509 25510 partial = this.info.likelyLocales[new Locale(undefined, locale.script, undefined).getSpec()]; 25511 if (typeof(partial) !== 'undefined') return new Locale(partial); 25512 25513 return locale; 25514 } 25515 25516 return new Locale(this.info.likelyLocales[locale.getSpec()]); 25517 }, 25518 25519 /** 25520 * Return an Locale instance that is fully specified based on partial information 25521 * given to the constructor of this locale matcher instance. For example, if the locale 25522 * spec given to this locale matcher instance is simply "ru" (for the Russian language), 25523 * then it will fill in the missing region and script tags and return a locale with 25524 * the specifier "ru-Cyrl-RU". (ie. Russian language, Cyrillic, Russian Federation). 25525 * Any one or two of the language, script, or region parts may be left unspecified, 25526 * and the other one or two parts will be filled in automatically. If this 25527 * class has no information about the given locale, then the locale of this 25528 * locale matcher instance is returned unchanged. 25529 * 25530 * @returns {Locale} the most likely completion of the partial locale given 25531 * to the constructor of this locale matcher instance 25532 */ 25533 getLikelyLocale: function () { 25534 return this._getLikelyLocale(this.locale); 25535 }, 25536 25537 /** 25538 * Return the degree that the given locale matches the current locale of this 25539 * matcher. This method returns an integer from 0 to 100. A value of 100 is 25540 * a 100% match, meaning that the two locales are exactly equivalent to each 25541 * other. (eg. "ja-JP" and "ja-JP") A value of 0 means that there 0% match or 25542 * that the two locales have nothing in common. (eg. "en-US" and "ja-JP") <p> 25543 * 25544 * Locale matching is not the same as equivalence, as the degree of matching 25545 * is returned. (See Locale.equals for equivalence.)<p> 25546 * 25547 * The match score is calculated based on matching the 4 locale components, 25548 * weighted by importance: 25549 * 25550 * <ul> 25551 * <li> language - this accounts for 50% of the match score 25552 * <li> region - accounts for 25% of the match score 25553 * <li> script - accounts for 20% of the match score 25554 * <li> variant - accounts for 5% of the match score 25555 * </ul> 25556 * 25557 * The score is affected by the following things: 25558 * 25559 * <ul> 25560 * <li> A large language score is given when the language components of the locales 25561 * match exactly. 25562 * <li> Higher language scores are given when the languages are linguistically 25563 * close to each other, such as dialects. 25564 * <li> A small score is given when two languages are in the same 25565 * linguistic family, but one is not a dialect of the other, such as German 25566 * and Dutch. 25567 * <li> A large region score is given when two locales share the same region. 25568 * <li> A smaller region score is given when one region is contained within 25569 * another. For example, Hong Kong is part of China, so a moderate score is 25570 * given instead of a full score. 25571 * <li> A small score is given if two regions are geographically close to 25572 * each other or are tied by history. For example, Ireland and Great Britain 25573 * are both adjacent and tied by history, so they receive a moderate score. 25574 * <li> A high script score is given if the two locales share the same script. 25575 * The legibility of a common script means that there is some small kinship of the 25576 * different languages. 25577 * <li> A high variant score is given if the two locales share the same 25578 * variant. Full score is given when both locales have no variant at all. 25579 * <li> Locale components that are unspecified in both locales are given high 25580 * scores. 25581 * <li> Locales where a particular locale component is missing in only one 25582 * locale can still match when the default for that locale component matches 25583 * the component in the other locale. The 25584 * default value for the missing component is determined using the likely locales 25585 * data. (See getLikelyLocale()) For example, "en-US" and "en-Latn-US" receive 25586 * a high script score because the default script for "en" is "Latn". 25587 * </ul> 25588 * 25589 * The intention of this method is that it can be used to determine 25590 * compatibility of locales. For example, when a user signs up for an 25591 * account on a web site, the locales that the web site supports and 25592 * the locale of the user's browser may differ, and the site needs to 25593 * pick the best locale to show the user. Let's say the 25594 * web site supports a selection of European languages such as "it-IT", 25595 * "fr-FR", "de-DE", and "en-GB". The user's 25596 * browser may be set to "it-CH". The web site code can then match "it-CH" 25597 * against each of the supported locales to find the one with the 25598 * highest score. In 25599 * this case, the best match would be "it-IT" because it shares a 25600 * language and script in common with "it-CH" and differs only in the region 25601 * component. It is not a 100% match, but it is pretty good. The web site 25602 * may decide if the match scores all fall 25603 * below a chosen threshold (perhaps 50%?), it should show the user the 25604 * default language "en-GB", because that is probably a better choice 25605 * than any other supported locale.<p> 25606 * 25607 * @param {Locale} locale the other locale to match against the current one 25608 * @return {number} an integer from 0 to 100 that indicates the degree to 25609 * which these locales match each other 25610 */ 25611 match: function(locale) { 25612 var other = new Locale(locale); 25613 var scores = [0, 0, 0, 0]; 25614 var thisfull, otherfull, i; 25615 25616 if (this.locale.language === other.language) { 25617 scores[0] = 100; 25618 } else { 25619 if (!this.locale.language || !other.language) { 25620 // check for default language 25621 thisfull = this.getLikelyLocale(); 25622 otherfull = new Locale(this.info.likelyLocales[other.getSpec()] || other.getSpec()); 25623 if (thisfull.language === otherfull.language) { 25624 scores[0] = 100; 25625 } 25626 } else { 25627 // check for macro languages 25628 var mlthis = this.info.macroLanguagesReverse[this.locale.language] || this.locale.language; 25629 var mlother = this.info.macroLanguagesReverse[other.language] || other.language; 25630 if (mlthis === mlother) { 25631 scores[0] = 90; 25632 } else { 25633 // check for mutual intelligibility 25634 var pair = this.locale.language + "-" + other.language; 25635 scores[0] = this.info.mutualIntelligibility[pair] || 0; 25636 } 25637 } 25638 } 25639 25640 if (this.locale.script === other.script) { 25641 scores[1] = 100; 25642 } else { 25643 if (!this.locale.script || !other.script) { 25644 // check for default script 25645 thisfull = this.locale.script ? this.locale : new Locale(this.info.likelyLocales[this.locale.language]); 25646 otherfull = other.script ? other : new Locale(this.info.likelyLocales[other.language]); 25647 if (thisfull.script === otherfull.script) { 25648 scores[1] = 100; 25649 } 25650 } 25651 } 25652 25653 if (this.locale.region === other.region) { 25654 scores[2] = 100; 25655 } else { 25656 if (!this.locale.region || !other.region) { 25657 // check for default region 25658 thisfull = this.getLikelyLocale(); 25659 otherfull = new Locale(this.info.likelyLocales[other.getSpec()] || other.getSpec()); 25660 if (thisfull.region === otherfull.region) { 25661 scores[2] = 100; 25662 } 25663 } else { 25664 // check for containment 25665 var containers = this.info.territoryContainmentReverse[this.locale.region] || []; 25666 // end at 1 because 0 is "001" which is "the whole world" -- which is not useful 25667 for (i = containers.length-1; i > 0; i--) { 25668 var container = this.info.territoryContainment[containers[i]]; 25669 if (container && container.indexOf(other.region) > -1) { 25670 // same area only accounts for 20% of the region score 25671 scores[2] = ((i+1) * 100 / containers.length) * 0.2; 25672 break; 25673 } 25674 } 25675 } 25676 } 25677 25678 if (this.locale.variant === other.variant) { 25679 scores[3] = 100; 25680 } 25681 25682 var total = 0; 25683 25684 for (i = 0; i < 4; i++) { 25685 total += scores[i] * componentWeights[i]; 25686 } 25687 25688 return Math.round(total); 25689 }, 25690 25691 /** 25692 * Return the macrolanguage associated with this locale. If the 25693 * locale's language is not part of a macro-language, then the 25694 * locale's language is returned as-is. 25695 * 25696 * @returns {string} the ISO code for the macrolanguage associated 25697 * with this locale, or language of the locale 25698 */ 25699 getMacroLanguage: function() { 25700 return this.info.macroLanguagesReverse[this.locale.language] || this.locale.language; 25701 }, 25702 25703 /** 25704 * @private 25705 * Return the containment array for the given region code. 25706 */ 25707 _getRegionContainment: function(region) { 25708 return this.info.territoryContainmentReverse[region] || [] 25709 }, 25710 25711 /** 25712 * Return the list of regions that this locale is contained within. Regions are 25713 * nested, so locales can be in multiple regions. (eg. US is in Northern North 25714 * America, North America, the Americas, the World.) Most regions are specified 25715 * using UN.49 region numbers, though some, like "EU", are letters. If the 25716 * locale is underspecified, this method will use the most likely locale method 25717 * to get the region first. For example, the locale "ja" (Japanese) is most 25718 * likely "ja-JP" (Japanese for Japan), and the region containment info for Japan 25719 * is returned. 25720 * 25721 * @returns {Array.<string>} an array of region specifiers that this locale is within 25722 */ 25723 getRegionContainment: function() { 25724 var region = this.locale.region || this.getLikelyLocale().region; 25725 return this._getRegionContainment(region); 25726 }, 25727 25728 /** 25729 * Find the smallest region that contains both the current locale and the other locale. 25730 * If the current or other locales are underspecified, this method will use the most 25731 * likely locale method 25732 * to get their regions first. For example, the locale "ja" (Japanese) is most 25733 * likely "ja-JP" (Japanese for Japan), and the region containment info for Japan 25734 * is checked against the other locale's region containment info. 25735 * 25736 * @param {string|Locale} otherLocale a locale specifier or a Locale instance to 25737 * compare against 25738 * @returns {string} the region specifier of the smallest region containing both the 25739 * current locale and other locale 25740 */ 25741 smallestCommonRegion: function(otherLocale) { 25742 if (typeof(otherLocale) === "undefined") return "001"; 25743 25744 var thisRegion = this.locale.region || this.getLikelyLocale().region; 25745 var otherLoc = typeof(otherLocale) === "string" ? new Locale(otherLocale) : otherLocale; 25746 var otherRegion = this._getLikelyLocale(otherLoc).region; 25747 25748 var thisRegions = this._getRegionContainment(thisRegion); 25749 var otherRegions = this._getRegionContainment(otherRegion); 25750 25751 // region containment arrays are arranged from largest to smallest, so start 25752 // at the end of the array 25753 for (var i = thisRegions.length-1; i > 0; i--) { 25754 if (otherRegions.indexOf(thisRegions[i]) > -1) { 25755 return thisRegions[i]; 25756 } 25757 } 25758 25759 // this default should never be reached because the world should be common to all regions 25760 return "001"; 25761 } 25762 }; 25763 25764 25765 25766 /*< CaseMapper.js */ 25767 /* 25768 * caseMapper.js - define upper- and lower-case mapper 25769 * 25770 * Copyright © 2014-2015, JEDLSoft 25771 * 25772 * Licensed under the Apache License, Version 2.0 (the "License"); 25773 * you may not use this file except in compliance with the License. 25774 * You may obtain a copy of the License at 25775 * 25776 * http://www.apache.org/licenses/LICENSE-2.0 25777 * 25778 * Unless required by applicable law or agreed to in writing, software 25779 * distributed under the License is distributed on an "AS IS" BASIS, 25780 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25781 * 25782 * See the License for the specific language governing permissions and 25783 * limitations under the License. 25784 */ 25785 25786 // !depends Locale.js IString.js 25787 25788 25789 25790 /** 25791 * @class 25792 * Create a new string mapper instance that maps strings to upper or 25793 * lower case. This mapping will work for any string as characters 25794 * that have no case will be returned unchanged.<p> 25795 * 25796 * The options may contain any of the following properties: 25797 * 25798 * <ul> 25799 * <li><i>locale</i> - locale to use when loading the mapper. Some maps are 25800 * locale-dependent, and this locale selects the right one. Default if this is 25801 * not specified is the current locale. 25802 * 25803 * <li><i>direction</i> - "toupper" for upper-casing, or "tolower" for lower-casing. 25804 * Default if not specified is "toupper". 25805 * </ul> 25806 * 25807 * 25808 * @constructor 25809 * @param {Object=} options options to initialize this mapper 25810 */ 25811 var CaseMapper = function (options) { 25812 this.up = true; 25813 this.locale = new Locale(); 25814 25815 if (options) { 25816 if (typeof(options.locale) !== 'undefined') { 25817 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 25818 } 25819 25820 this.up = (!options.direction || options.direction === "toupper"); 25821 } 25822 25823 this.mapData = this.up ? { 25824 "ß": "SS", // German 25825 'ΐ': 'Ι', // Greek 25826 'ά': 'Α', 25827 'έ': 'Ε', 25828 'ή': 'Η', 25829 'ί': 'Ι', 25830 'ΰ': 'Υ', 25831 'ϊ': 'Ι', 25832 'ϋ': 'Υ', 25833 'ό': 'Ο', 25834 'ύ': 'Υ', 25835 'ώ': 'Ω', 25836 'Ӏ': 'Ӏ', // Russian and slavic languages 25837 'ӏ': 'Ӏ' 25838 } : { 25839 'Ӏ': 'Ӏ' // Russian and slavic languages 25840 }; 25841 25842 switch (this.locale.getLanguage()) { 25843 case "az": 25844 case "tr": 25845 case "crh": 25846 case "kk": 25847 case "krc": 25848 case "tt": 25849 var lower = "iı"; 25850 var upper = "İI"; 25851 this._setUpMap(lower, upper); 25852 break; 25853 } 25854 25855 if (ilib._getBrowser() === "ie" || ilib._getBrowser() === "Edge") { 25856 // IE is missing these mappings for some reason 25857 if (this.up) { 25858 this.mapData['ς'] = 'Σ'; 25859 } 25860 this._setUpMap("ⲁⲃⲅⲇⲉⲋⲍⲏⲑⲓⲕⲗⲙⲛⲝⲟⲡⲣⲥⲧⲩⲫⲭⲯⲱⳁⳉⳋ", "ⲀⲂⲄⲆⲈⲊⲌⲎⲐⲒⲔⲖⲘⲚⲜⲞⲠⲢⲤⲦⲨⲪⲬⲮⲰⳀⳈⳊ"); // Coptic 25861 // Georgian Nuskhuri <-> Asomtavruli 25862 this._setUpMap("ⴀⴁⴂⴃⴄⴅⴆⴇⴈⴉⴊⴋⴌⴍⴎⴏⴐⴑⴒⴓⴔⴕⴖⴗⴘⴙⴚⴛⴜⴝⴞⴟⴠⴡⴢⴣⴤⴥ", "ႠႡႢႣႤႥႦႧႨႩႪႫႬႭႮႯႰႱႲႳႴႵႶႷႸႹႺႻႼႽႾႿჀჁჂჃჄჅ"); 25863 } 25864 }; 25865 25866 CaseMapper.prototype = { 25867 /** 25868 * @private 25869 */ 25870 _charMapper: function(string) { 25871 if (!string) { 25872 return string; 25873 } 25874 var input = (typeof(string) === 'string') ? new IString(string) : string.toString(); 25875 var ret = ""; 25876 var it = input.charIterator(); 25877 var c; 25878 25879 while (it.hasNext()) { 25880 c = it.next(); 25881 if (!this.up && c === 'Σ') { 25882 if (it.hasNext()) { 25883 c = it.next(); 25884 var code = c.charCodeAt(0); 25885 // if the next char is not a greek letter, this is the end of the word so use the 25886 // final form of sigma. Otherwise, use the mid-word form. 25887 ret += ((code < 0x0388 && code !== 0x0386) || code > 0x03CE) ? 'ς' : 'σ'; 25888 ret += c.toLowerCase(); 25889 } else { 25890 // no next char means this is the end of the word, so use the final form of sigma 25891 ret += 'ς'; 25892 } 25893 } else { 25894 if (this.mapData[c]) { 25895 ret += this.mapData[c]; 25896 } else { 25897 ret += this.up ? c.toUpperCase() : c.toLowerCase(); 25898 } 25899 } 25900 } 25901 25902 return ret; 25903 }, 25904 25905 /** @private */ 25906 _setUpMap: function(lower, upper) { 25907 var from, to; 25908 if (this.up) { 25909 from = lower; 25910 to = upper; 25911 } else { 25912 from = upper; 25913 to = lower; 25914 } 25915 for (var i = 0; i < upper.length; i++) { 25916 this.mapData[from[i]] = to[i]; 25917 } 25918 }, 25919 25920 /** 25921 * Return the locale that this mapper was constructed with. 25922 * @returns {Locale} the locale that this mapper was constructed with 25923 */ 25924 getLocale: function () { 25925 return this.locale; 25926 }, 25927 25928 /** 25929 * Map a string to lower case in a locale-sensitive manner. 25930 * 25931 * @param {string|undefined} string 25932 * @return {string|undefined} 25933 */ 25934 map: function (string) { 25935 return this._charMapper(string); 25936 } 25937 }; 25938 25939 25940 25941 /*< NumberingPlan.js */ 25942 /* 25943 * NumPlan.js - Represent a phone numbering plan. 25944 * 25945 * Copyright © 2014-2015, JEDLSoft 25946 * 25947 * Licensed under the Apache License, Version 2.0 (the "License"); 25948 * you may not use this file except in compliance with the License. 25949 * You may obtain a copy of the License at 25950 * 25951 * http://www.apache.org/licenses/LICENSE-2.0 25952 * 25953 * Unless required by applicable law or agreed to in writing, software 25954 * distributed under the License is distributed on an "AS IS" BASIS, 25955 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25956 * 25957 * See the License for the specific language governing permissions and 25958 * limitations under the License. 25959 */ 25960 25961 /* 25962 !depends 25963 ilib.js 25964 Locale.js 25965 Utils.js 25966 */ 25967 25968 // !data numplan 25969 25970 25971 /** 25972 * @class 25973 * Create a numbering plan information instance for a particular country's plan.<p> 25974 * 25975 * The options may contain any of the following properties: 25976 * 25977 * <ul> 25978 * <li><i>locale</i> - locale for which the numbering plan is sought. This locale 25979 * will be mapped to the actual numbering plan, which may be shared amongst a 25980 * number of countries. 25981 * 25982 * <li>onLoad - a callback function to call when the date format object is fully 25983 * loaded. When the onLoad option is given, the DateFmt object will attempt to 25984 * load any missing locale data using the ilib loader callback. 25985 * When the constructor is done (even if the data is already preassembled), the 25986 * onLoad function is called with the current instance as a parameter, so this 25987 * callback can be used with preassembled or dynamic loading or a mix of the two. 25988 * 25989 * <li>sync - tell whether to load any missing locale data synchronously or 25990 * asynchronously. If this option is given as "false", then the "onLoad" 25991 * callback must be given, as the instance returned from this constructor will 25992 * not be usable for a while. 25993 * 25994 * <li><i>loadParams</i> - an object containing parameters to pass to the 25995 * loader callback function when locale data is missing. The parameters are not 25996 * interpretted or modified in any way. They are simply passed along. The object 25997 * may contain any property/value pairs as long as the calling code is in 25998 * agreement with the loader callback function as to what those parameters mean. 25999 * </ul> 26000 * 26001 * @private 26002 * @constructor 26003 * @param {Object} options options governing the way this plan is loaded 26004 */ 26005 var NumberingPlan = function (options) { 26006 var sync = true, 26007 loadParams = {}; 26008 26009 this.locale = new Locale(); 26010 26011 if (options) { 26012 if (options.locale) { 26013 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 26014 } 26015 26016 if (typeof(options.sync) !== 'undefined') { 26017 sync = !!options.sync; 26018 } 26019 26020 if (options.loadParams) { 26021 loadParams = options.loadParams; 26022 } 26023 } 26024 26025 Utils.loadData({ 26026 name: "numplan.json", 26027 object: "NumberingPlan", 26028 locale: this.locale, 26029 sync: sync, 26030 loadParams: loadParams, 26031 callback: ilib.bind(this, function (npdata) { 26032 if (!npdata) { 26033 npdata = { 26034 "region": "XX", 26035 "skipTrunk": false, 26036 "trunkCode": "0", 26037 "iddCode": "00", 26038 "dialingPlan": "closed", 26039 "commonFormatChars": " ()-./", 26040 "fieldLengths": { 26041 "areaCode": 0, 26042 "cic": 0, 26043 "mobilePrefix": 0, 26044 "serviceCode": 0 26045 } 26046 }; 26047 } 26048 26049 /** 26050 * @type {{ 26051 * region:string, 26052 * skipTrunk:boolean, 26053 * trunkCode:string, 26054 * iddCode:string, 26055 * dialingPlan:string, 26056 * commonFormatChars:string, 26057 * fieldLengths:Object.<string,number>, 26058 * contextFree:boolean, 26059 * findExtensions:boolean, 26060 * trunkRequired:boolean, 26061 * extendedAreaCodes:boolean 26062 * }} 26063 */ 26064 this.npdata = npdata; 26065 if (options && typeof(options.onLoad) === 'function') { 26066 options.onLoad(this); 26067 } 26068 }) 26069 }); 26070 }; 26071 26072 NumberingPlan.prototype = { 26073 /** 26074 * Return the name of this plan. This may be different than the 26075 * name of the region because sometimes multiple countries share 26076 * the same plan. 26077 * @return {string} the name of the plan 26078 */ 26079 getName: function() { 26080 return this.npdata.region; 26081 }, 26082 26083 /** 26084 * Return the trunk code of the current plan as a string. 26085 * @return {string|undefined} the trunk code of the plan or 26086 * undefined if there is no trunk code in this plan 26087 */ 26088 getTrunkCode: function() { 26089 return this.npdata.trunkCode; 26090 }, 26091 26092 /** 26093 * Return the international direct dialing code of this plan. 26094 * @return {string} the IDD code of this plan 26095 */ 26096 getIDDCode: function() { 26097 return this.npdata.iddCode; 26098 }, 26099 26100 /** 26101 * Return the plan style for this plan. The plan style may be 26102 * one of: 26103 * 26104 * <ul> 26105 * <li>"open" - area codes may be left off if the caller is 26106 * dialing to another number within the same area code 26107 * <li>"closed" - the area code must always be specified, even 26108 * if calling another number within the same area code 26109 * </ul> 26110 * 26111 * @return {string} the plan style, "open" or "closed" 26112 */ 26113 getPlanStyle: function() { 26114 return this.npdata.dialingPlan; 26115 }, 26116 /** [Need Comment] 26117 * Return a contextFree 26118 * 26119 * @return {boolean} 26120 */ 26121 getContextFree: function() { 26122 return this.npdata.contextFree; 26123 }, 26124 /** [Need Comment] 26125 * Return a findExtensions 26126 * 26127 * @return {boolean} 26128 */ 26129 getFindExtensions: function() { 26130 return this.npdata.findExtensions; 26131 }, 26132 /** [Need Comment] 26133 * Return a skipTrunk 26134 * 26135 * @return {boolean} 26136 */ 26137 getSkipTrunk: function() { 26138 return this.npdata.skipTrunk; 26139 }, 26140 /** [Need Comment] 26141 * Return a skipTrunk 26142 * 26143 * @return {boolean} 26144 */ 26145 getTrunkRequired: function() { 26146 return this.npdata.trunkRequired; 26147 }, 26148 /** 26149 * Return true if this plan uses extended area codes. 26150 * @return {boolean} true if the plan uses extended area codes 26151 */ 26152 getExtendedAreaCode: function() { 26153 return this.npdata.extendedAreaCodes; 26154 }, 26155 /** 26156 * Return a string containing all of the common format characters 26157 * used to format numbers. 26158 * @return {string} the common format characters fused in this locale 26159 */ 26160 getCommonFormatChars: function() { 26161 return this.npdata.commonFormatChars; 26162 }, 26163 26164 /** 26165 * Return the length of the field with the given name. If the length 26166 * is returned as 0, this means it is variable length. 26167 * 26168 * @param {string} field name of the field for which the length is 26169 * being sought 26170 * @return {number} if positive, this gives the length of the given 26171 * field. If zero, the field is variable length. If negative, the 26172 * field is not known. 26173 */ 26174 getFieldLength: function (field) { 26175 var dataField = this.npdata.fieldLengths; 26176 26177 return dataField[field]; 26178 } 26179 }; 26180 26181 26182 /*< PhoneLocale.js */ 26183 /* 26184 * phoneloc.js - Represent a phone locale object. 26185 * 26186 * Copyright © 2014-2015, JEDLSoft 26187 * 26188 * Licensed under the Apache License, Version 2.0 (the "License"); 26189 * you may not use this file except in compliance with the License. 26190 * You may obtain a copy of the License at 26191 * 26192 * http://www.apache.org/licenses/LICENSE-2.0 26193 * 26194 * Unless required by applicable law or agreed to in writing, software 26195 * distributed under the License is distributed on an "AS IS" BASIS, 26196 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26197 * 26198 * See the License for the specific language governing permissions and 26199 * limitations under the License. 26200 */ 26201 26202 /* 26203 !depends 26204 ilib.js 26205 Locale.js 26206 Utils.js 26207 */ 26208 26209 // !data phoneloc 26210 26211 26212 /** 26213 * @class 26214 * Extension of the locale class that has extra methods to map various numbers 26215 * related to phone number parsing. 26216 * 26217 * @param {Object} options Options that govern how this phone locale works 26218 * 26219 * @private 26220 * @constructor 26221 * @extends Locale 26222 */ 26223 var PhoneLocale = function(options) { 26224 var region, 26225 mcc, 26226 cc, 26227 sync = true, 26228 loadParams = {}, 26229 locale; 26230 26231 locale = (options && options.locale) || ilib.getLocale(); 26232 26233 this.parent.call(this, locale); 26234 26235 region = this.region; 26236 26237 if (options) { 26238 if (typeof(options.mcc) !== 'undefined') { 26239 mcc = options.mcc; 26240 } 26241 26242 if (typeof(options.countryCode) !== 'undefined') { 26243 cc = options.countryCode; 26244 } 26245 26246 if (typeof(options.sync) !== 'undefined') { 26247 sync = !!options.sync; 26248 } 26249 26250 if (options.loadParams) { 26251 loadParams = options.loadParams; 26252 } 26253 } 26254 26255 Utils.loadData({ 26256 name: "phoneloc.json", 26257 object: "PhoneLocale", 26258 nonlocale: true, 26259 sync: sync, 26260 loadParams: loadParams, 26261 callback: ilib.bind(this, function (data) { 26262 /** @type {{mcc2reg:Object.<string,string>,cc2reg:Object.<string,string>,reg2cc:Object.<string,string>,area2reg:Object.<string,string>}} */ 26263 this.mappings = data; 26264 26265 if (typeof(mcc) !== 'undefined') { 26266 region = this.mappings.mcc2reg[mcc]; 26267 } 26268 26269 if (typeof(cc) !== 'undefined') { 26270 region = this.mappings.cc2reg[cc]; 26271 } 26272 26273 if (!region) { 26274 region = "XX"; 26275 } 26276 26277 this.region = this._normPhoneReg(region); 26278 this._genSpec(); 26279 26280 if (options && typeof(options.onLoad) === 'function') { 26281 options.onLoad(this); 26282 } 26283 }) 26284 }); 26285 }; 26286 26287 PhoneLocale.prototype = new Locale(); 26288 PhoneLocale.prototype.parent = Locale; 26289 PhoneLocale.prototype.constructor = PhoneLocale; 26290 26291 /** 26292 * Map a mobile carrier code to a region code. 26293 * 26294 * @static 26295 * @package 26296 * @param {string|undefined} mcc the MCC to map 26297 * @return {string|undefined} the region code 26298 */ 26299 26300 PhoneLocale.prototype._mapMCCtoRegion = function(mcc) { 26301 if (!mcc) { 26302 return undefined; 26303 } 26304 return this.mappings.mcc2reg && this.mappings.mcc2reg[mcc] || "XX"; 26305 }; 26306 26307 /** 26308 * Map a country code to a region code. 26309 * 26310 * @static 26311 * @package 26312 * @param {string|undefined} cc the country code to map 26313 * @return {string|undefined} the region code 26314 */ 26315 PhoneLocale.prototype._mapCCtoRegion = function(cc) { 26316 if (!cc) { 26317 return undefined; 26318 } 26319 return this.mappings.cc2reg && this.mappings.cc2reg[cc] || "XX"; 26320 }; 26321 26322 /** 26323 * Map a region code to a country code. 26324 * 26325 * @static 26326 * @package 26327 * @param {string|undefined} region the region code to map 26328 * @return {string|undefined} the country code 26329 */ 26330 PhoneLocale.prototype._mapRegiontoCC = function(region) { 26331 if (!region) { 26332 return undefined; 26333 } 26334 return this.mappings.reg2cc && this.mappings.reg2cc[region] || "0"; 26335 }; 26336 26337 /** 26338 * Map a country code to a region code. 26339 * 26340 * @static 26341 * @package 26342 * @param {string|undefined} cc the country code to map 26343 * @param {string|undefined} area the area code within the country code's numbering plan 26344 * @return {string|undefined} the region code 26345 */ 26346 PhoneLocale.prototype._mapAreatoRegion = function(cc, area) { 26347 if (!cc) { 26348 return undefined; 26349 } 26350 if (cc in this.mappings.area2reg) { 26351 return this.mappings.area2reg[cc][area] || this.mappings.area2reg[cc]["default"]; 26352 } else { 26353 return this.mappings.cc2reg[cc]; 26354 } 26355 }; 26356 26357 /** 26358 * Return the region that controls the dialing plan in the given 26359 * region. (ie. the "normalized phone region".) 26360 * 26361 * @static 26362 * @package 26363 * @param {string} region the region code to normalize 26364 * @return {string} the normalized region code 26365 */ 26366 PhoneLocale.prototype._normPhoneReg = function(region) { 26367 var norm; 26368 26369 // Map all NANP regions to the right region, so that they get parsed using the 26370 // correct state table 26371 switch (region) { 26372 case "US": // usa 26373 case "CA": // canada 26374 case "AG": // antigua and barbuda 26375 case "BS": // bahamas 26376 case "BB": // barbados 26377 case "DM": // dominica 26378 case "DO": // dominican republic 26379 case "GD": // grenada 26380 case "JM": // jamaica 26381 case "KN": // st. kitts and nevis 26382 case "LC": // st. lucia 26383 case "VC": // st. vincent and the grenadines 26384 case "TT": // trinidad and tobago 26385 case "AI": // anguilla 26386 case "BM": // bermuda 26387 case "VG": // british virgin islands 26388 case "KY": // cayman islands 26389 case "MS": // montserrat 26390 case "TC": // turks and caicos 26391 case "AS": // American Samoa 26392 case "VI": // Virgin Islands, U.S. 26393 case "PR": // Puerto Rico 26394 case "MP": // Northern Mariana Islands 26395 case "T:": // East Timor 26396 case "GU": // Guam 26397 norm = "US"; 26398 break; 26399 26400 // these all use the Italian dialling plan 26401 case "IT": // italy 26402 case "SM": // san marino 26403 case "VA": // vatican city 26404 norm = "IT"; 26405 break; 26406 26407 // all the French dependencies are on the French dialling plan 26408 case "FR": // france 26409 case "GF": // french guiana 26410 case "MQ": // martinique 26411 case "GP": // guadeloupe, 26412 case "BL": // saint barthélemy 26413 case "MF": // saint martin 26414 case "RE": // réunion, mayotte 26415 norm = "FR"; 26416 break; 26417 default: 26418 norm = region; 26419 break; 26420 } 26421 return norm; 26422 }; 26423 26424 26425 /*< PhoneHandlerFactory.js */ 26426 /* 26427 * handler.js - Handle phone number parse states 26428 * 26429 * Copyright © 2014-2015, JEDLSoft 26430 * 26431 * Licensed under the Apache License, Version 2.0 (the "License"); 26432 * you may not use this file except in compliance with the License. 26433 * You may obtain a copy of the License at 26434 * 26435 * http://www.apache.org/licenses/LICENSE-2.0 26436 * 26437 * Unless required by applicable law or agreed to in writing, software 26438 * distributed under the License is distributed on an "AS IS" BASIS, 26439 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26440 * 26441 * See the License for the specific language governing permissions and 26442 * limitations under the License. 26443 */ 26444 26445 26446 /** 26447 * @class 26448 * @private 26449 * @constructor 26450 */ 26451 var PhoneHandler = function () { 26452 return this; 26453 }; 26454 26455 PhoneHandler.prototype = { 26456 /** 26457 * @private 26458 * @param {string} number phone number 26459 * @param {Object} fields the fields that have been extracted so far 26460 * @param {Object} regionSettings settings used to parse the rest of the number 26461 */ 26462 processSubscriberNumber: function(number, fields, regionSettings) { 26463 var last; 26464 26465 last = number.search(/[xwtp,;]/i); // last digit of the local number 26466 26467 if (last > -1) { 26468 if (last > 0) { 26469 fields.subscriberNumber = number.substring(0, last); 26470 } 26471 // strip x's which are there to indicate a break between the local subscriber number and the extension, but 26472 // are not themselves a dialable character 26473 fields.extension = number.substring(last).replace('x', ''); 26474 } else { 26475 if (number.length) { 26476 fields.subscriberNumber = number; 26477 } 26478 } 26479 26480 if (regionSettings.plan.getFieldLength('maxLocalLength') && 26481 fields.subscriberNumber && 26482 fields.subscriberNumber.length > regionSettings.plan.getFieldLength('maxLocalLength')) { 26483 fields.invalid = true; 26484 } 26485 }, 26486 /** 26487 * @private 26488 * @param {string} fieldName 26489 * @param {number} length length of phone number 26490 * @param {string} number phone number 26491 * @param {number} currentChar currentChar to be parsed 26492 * @param {Object} fields the fields that have been extracted so far 26493 * @param {Object} regionSettings settings used to parse the rest of the number 26494 * @param {boolean} noExtractTrunk 26495 */ 26496 processFieldWithSubscriberNumber: function(fieldName, length, number, currentChar, fields, regionSettings, noExtractTrunk) { 26497 var ret, end; 26498 26499 if (length !== undefined && length > 0) { 26500 // fixed length 26501 end = length; 26502 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 26503 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 26504 } 26505 } else { 26506 // variable length 26507 // the setting is the negative of the length to add, so subtract to make it positive 26508 end = currentChar + 1 - length; 26509 } 26510 26511 if (fields[fieldName] !== undefined) { 26512 // we have a spurious recognition, because this number already contains that field! So, just put 26513 // everything into the subscriberNumber as the default 26514 this.processSubscriberNumber(number, fields, regionSettings); 26515 } else { 26516 fields[fieldName] = number.substring(0, end); 26517 if (number.length > end) { 26518 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 26519 } 26520 } 26521 26522 ret = { 26523 number: "" 26524 }; 26525 26526 return ret; 26527 }, 26528 /** 26529 * @private 26530 * @param {string} fieldName 26531 * @param {number} length length of phone number 26532 * @param {string} number phone number 26533 * @param {number} currentChar currentChar to be parsed 26534 * @param {Object} fields the fields that have been extracted so far 26535 * @param {Object} regionSettings settings used to parse the rest of the number 26536 */ 26537 processField: function(fieldName, length, number, currentChar, fields, regionSettings) { 26538 var ret = {}, end; 26539 26540 if (length !== undefined && length > 0) { 26541 // fixed length 26542 end = length; 26543 if (regionSettings.plan.getTrunkCode() === "0" && number.charAt(0) === "0") { 26544 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 26545 } 26546 } else { 26547 // variable length 26548 // the setting is the negative of the length to add, so subtract to make it positive 26549 end = currentChar + 1 - length; 26550 } 26551 26552 if (fields[fieldName] !== undefined) { 26553 // we have a spurious recognition, because this number already contains that field! So, just put 26554 // everything into the subscriberNumber as the default 26555 this.processSubscriberNumber(number, fields, regionSettings); 26556 ret.number = ""; 26557 } else { 26558 fields[fieldName] = number.substring(0, end); 26559 ret.number = (number.length > end) ? number.substring(end) : ""; 26560 } 26561 26562 return ret; 26563 }, 26564 /** 26565 * @private 26566 * @param {string} number phone number 26567 * @param {number} currentChar currentChar to be parsed 26568 * @param {Object} fields the fields that have been extracted so far 26569 * @param {Object} regionSettings settings used to parse the rest of the number 26570 */ 26571 trunk: function(number, currentChar, fields, regionSettings) { 26572 var ret, trunkLength; 26573 26574 if (fields.trunkAccess !== undefined) { 26575 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 26576 this.processSubscriberNumber(number, fields, regionSettings); 26577 number = ""; 26578 } else { 26579 trunkLength = regionSettings.plan.getTrunkCode().length; 26580 fields.trunkAccess = number.substring(0, trunkLength); 26581 number = (number.length > trunkLength) ? number.substring(trunkLength) : ""; 26582 } 26583 26584 ret = { 26585 number: number 26586 }; 26587 26588 return ret; 26589 }, 26590 /** 26591 * @private 26592 * @param {string} number phone number 26593 * @param {number} currentChar currentChar to be parsed 26594 * @param {Object} fields the fields that have been extracted so far 26595 * @param {Object} regionSettings settings used to parse the rest of the number 26596 */ 26597 plus: function(number, currentChar, fields, regionSettings) { 26598 var ret = {}; 26599 26600 if (fields.iddPrefix !== undefined) { 26601 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 26602 this.processSubscriberNumber(number, fields, regionSettings); 26603 ret.number = ""; 26604 } else { 26605 // found the idd prefix, so save it and cause the function to parse the next part 26606 // of the number with the idd table 26607 fields.iddPrefix = number.substring(0, 1); 26608 26609 ret = { 26610 number: number.substring(1), 26611 table: 'idd' // shared subtable that parses the country code 26612 }; 26613 } 26614 return ret; 26615 }, 26616 /** 26617 * @private 26618 * @param {string} number phone number 26619 * @param {number} currentChar currentChar to be parsed 26620 * @param {Object} fields the fields that have been extracted so far 26621 * @param {Object} regionSettings settings used to parse the rest of the number 26622 */ 26623 idd: function(number, currentChar, fields, regionSettings) { 26624 var ret = {}; 26625 26626 if (fields.iddPrefix !== undefined) { 26627 // What? We already have one? Okay, put the rest of this in the subscriber number as the default behaviour then. 26628 this.processSubscriberNumber(number, fields, regionSettings); 26629 ret.number = ""; 26630 } else { 26631 // found the idd prefix, so save it and cause the function to parse the next part 26632 // of the number with the idd table 26633 fields.iddPrefix = number.substring(0, currentChar+1); 26634 26635 ret = { 26636 number: number.substring(currentChar+1), 26637 table: 'idd' // shared subtable that parses the country code 26638 }; 26639 } 26640 26641 return ret; 26642 }, 26643 /** 26644 * @private 26645 * @param {string} number phone number 26646 * @param {number} currentChar currentChar to be parsed 26647 * @param {Object} fields the fields that have been extracted so far 26648 * @param {Object} regionSettings settings used to parse the rest of the number 26649 */ 26650 country: function(number, currentChar, fields, regionSettings) { 26651 var ret, cc; 26652 26653 // found the country code of an IDD number, so save it and cause the function to 26654 // parse the rest of the number with the regular table for this locale 26655 fields.countryCode = number.substring(0, currentChar+1); 26656 cc = fields.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 26657 // console.log("Found country code " + fields.countryCode + ". Switching to country " + locale.region + " to parse the rest of the number"); 26658 26659 ret = { 26660 number: number.substring(currentChar+1), 26661 countryCode: cc 26662 }; 26663 26664 return ret; 26665 }, 26666 /** 26667 * @private 26668 * @param {string} number phone number 26669 * @param {number} currentChar currentChar to be parsed 26670 * @param {Object} fields the fields that have been extracted so far 26671 * @param {Object} regionSettings settings used to parse the rest of the number 26672 */ 26673 cic: function(number, currentChar, fields, regionSettings) { 26674 return this.processField('cic', regionSettings.plan.getFieldLength('cic'), number, currentChar, fields, regionSettings); 26675 }, 26676 /** 26677 * @private 26678 * @param {string} number phone number 26679 * @param {number} currentChar currentChar to be parsed 26680 * @param {Object} fields the fields that have been extracted so far 26681 * @param {Object} regionSettings settings used to parse the rest of the number 26682 */ 26683 service: function(number, currentChar, fields, regionSettings) { 26684 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('serviceCode'), number, currentChar, fields, regionSettings, false); 26685 }, 26686 /** 26687 * @private 26688 * @param {string} number phone number 26689 * @param {number} currentChar currentChar to be parsed 26690 * @param {Object} fields the fields that have been extracted so far 26691 * @param {Object} regionSettings settings used to parse the rest of the number 26692 */ 26693 area: function(number, currentChar, fields, regionSettings) { 26694 var ret, last, end, localLength; 26695 26696 last = number.search(/[xwtp]/i); // last digit of the local number 26697 localLength = (last > -1) ? last : number.length; 26698 26699 if (regionSettings.plan.getFieldLength('areaCode') > 0) { 26700 // fixed length 26701 end = regionSettings.plan.getFieldLength('areaCode'); 26702 if (regionSettings.plan.getTrunkCode() === number.charAt(0)) { 26703 end += regionSettings.plan.getTrunkCode().length; // also extract the trunk access code 26704 localLength -= regionSettings.plan.getTrunkCode().length; 26705 } 26706 } else { 26707 // variable length 26708 // the setting is the negative of the length to add, so subtract to make it positive 26709 end = currentChar + 1 - regionSettings.plan.getFieldLength('areaCode'); 26710 } 26711 26712 // substring() extracts the part of the string up to but not including the end character, 26713 // so add one to compensate 26714 if (regionSettings.plan.getFieldLength('maxLocalLength') !== undefined) { 26715 if (fields.trunkAccess !== undefined || fields.mobilePrefix !== undefined || 26716 fields.countryCode !== undefined || 26717 localLength > regionSettings.plan.getFieldLength('maxLocalLength')) { 26718 // too long for a local number by itself, or a different final state already parsed out the trunk 26719 // or mobile prefix, then consider the rest of this number to be an area code + part of the subscriber number 26720 fields.areaCode = number.substring(0, end); 26721 if (number.length > end) { 26722 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 26723 } 26724 } else { 26725 // shorter than the length needed for a local number, so just consider it a local number 26726 this.processSubscriberNumber(number, fields, regionSettings); 26727 } 26728 } else { 26729 fields.areaCode = number.substring(0, end); 26730 if (number.length > end) { 26731 this.processSubscriberNumber(number.substring(end), fields, regionSettings); 26732 } 26733 } 26734 26735 // extensions are separated from the number by a dash in Germany 26736 if (regionSettings.plan.getFindExtensions() !== undefined && fields.subscriberNumber !== undefined) { 26737 var dash = fields.subscriberNumber.indexOf("-"); 26738 if (dash > -1) { 26739 fields.subscriberNumber = fields.subscriberNumber.substring(0, dash); 26740 fields.extension = fields.subscriberNumber.substring(dash+1); 26741 } 26742 } 26743 26744 ret = { 26745 number: "" 26746 }; 26747 26748 return ret; 26749 }, 26750 /** 26751 * @private 26752 * @param {string} number phone number 26753 * @param {number} currentChar currentChar to be parsed 26754 * @param {Object} fields the fields that have been extracted so far 26755 * @param {Object} regionSettings settings used to parse the rest of the number 26756 */ 26757 none: function(number, currentChar, fields, regionSettings) { 26758 var ret; 26759 26760 // this is a last resort function that is called when nothing is recognized. 26761 // When this happens, just put the whole stripped number into the subscriber number 26762 26763 if (number.length > 0) { 26764 this.processSubscriberNumber(number, fields, regionSettings); 26765 if (currentChar > 0 && currentChar < number.length) { 26766 // if we were part-way through parsing, and we hit an invalid digit, 26767 // indicate that the number could not be parsed properly 26768 fields.invalid = true; 26769 } 26770 } 26771 26772 ret = { 26773 number:"" 26774 }; 26775 26776 return ret; 26777 }, 26778 /** 26779 * @private 26780 * @param {string} number phone number 26781 * @param {number} currentChar currentChar to be parsed 26782 * @param {Object} fields the fields that have been extracted so far 26783 * @param {Object} regionSettings settings used to parse the rest of the number 26784 */ 26785 vsc: function(number, currentChar, fields, regionSettings) { 26786 var ret, length, end; 26787 26788 if (fields.vsc === undefined) { 26789 length = regionSettings.plan.getFieldLength('vsc') || 0; 26790 if (length !== undefined && length > 0) { 26791 // fixed length 26792 end = length; 26793 } else { 26794 // variable length 26795 // the setting is the negative of the length to add, so subtract to make it positive 26796 end = currentChar + 1 - length; 26797 } 26798 26799 // found a VSC code (ie. a "star code"), so save it and cause the function to 26800 // parse the rest of the number with the same table for this locale 26801 fields.vsc = number.substring(0, end); 26802 number = (number.length > end) ? "^" + number.substring(end) : ""; 26803 } else { 26804 // got it twice??? Okay, this is a bogus number then. Just put everything else into the subscriber number as the default 26805 this.processSubscriberNumber(number, fields, regionSettings); 26806 number = ""; 26807 } 26808 26809 // treat the rest of the number as if it were a completely new number 26810 ret = { 26811 number: number 26812 }; 26813 26814 return ret; 26815 }, 26816 /** 26817 * @private 26818 * @param {string} number phone number 26819 * @param {number} currentChar currentChar to be parsed 26820 * @param {Object} fields the fields that have been extracted so far 26821 * @param {Object} regionSettings settings used to parse the rest of the number 26822 */ 26823 cell: function(number, currentChar, fields, regionSettings) { 26824 return this.processFieldWithSubscriberNumber('mobilePrefix', regionSettings.plan.getFieldLength('mobilePrefix'), number, currentChar, fields, regionSettings, false); 26825 }, 26826 /** 26827 * @private 26828 * @param {string} number phone number 26829 * @param {number} currentChar currentChar to be parsed 26830 * @param {Object} fields the fields that have been extracted so far 26831 * @param {Object} regionSettings settings used to parse the rest of the number 26832 */ 26833 personal: function(number, currentChar, fields, regionSettings) { 26834 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('personal'), number, currentChar, fields, regionSettings, false); 26835 }, 26836 /** 26837 * @private 26838 * @param {string} number phone number 26839 * @param {number} currentChar currentChar to be parsed 26840 * @param {Object} fields the fields that have been extracted so far 26841 * @param {Object} regionSettings settings used to parse the rest of the number 26842 */ 26843 emergency: function(number, currentChar, fields, regionSettings) { 26844 return this.processFieldWithSubscriberNumber('emergency', regionSettings.plan.getFieldLength('emergency'), number, currentChar, fields, regionSettings, true); 26845 }, 26846 /** 26847 * @private 26848 * @param {string} number phone number 26849 * @param {number} currentChar currentChar to be parsed 26850 * @param {Object} fields the fields that have been extracted so far 26851 * @param {Object} regionSettings settings used to parse the rest of the number 26852 */ 26853 premium: function(number, currentChar, fields, regionSettings) { 26854 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('premium'), number, currentChar, fields, regionSettings, false); 26855 }, 26856 /** 26857 * @private 26858 * @param {string} number phone number 26859 * @param {number} currentChar currentChar to be parsed 26860 * @param {Object} fields the fields that have been extracted so far 26861 * @param {Object} regionSettings settings used to parse the rest of the number 26862 */ 26863 special: function(number, currentChar, fields, regionSettings) { 26864 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('special'), number, currentChar, fields, regionSettings, false); 26865 }, 26866 /** 26867 * @private 26868 * @param {string} number phone number 26869 * @param {number} currentChar currentChar to be parsed 26870 * @param {Object} fields the fields that have been extracted so far 26871 * @param {Object} regionSettings settings used to parse the rest of the number 26872 */ 26873 service2: function(number, currentChar, fields, regionSettings) { 26874 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service2'), number, currentChar, fields, regionSettings, false); 26875 }, 26876 /** 26877 * @private 26878 * @param {string} number phone number 26879 * @param {number} currentChar currentChar to be parsed 26880 * @param {Object} fields the fields that have been extracted so far 26881 * @param {Object} regionSettings settings used to parse the rest of the number 26882 */ 26883 service3: function(number, currentChar, fields, regionSettings) { 26884 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service3'), number, currentChar, fields, regionSettings, false); 26885 }, 26886 /** 26887 * @private 26888 * @param {string} number phone number 26889 * @param {number} currentChar currentChar to be parsed 26890 * @param {Object} fields the fields that have been extracted so far 26891 * @param {Object} regionSettings settings used to parse the rest of the number 26892 */ 26893 service4: function(number, currentChar, fields, regionSettings) { 26894 return this.processFieldWithSubscriberNumber('serviceCode', regionSettings.plan.getFieldLength('service4'), number, currentChar, fields, regionSettings, false); 26895 }, 26896 /** 26897 * @private 26898 * @param {string} number phone number 26899 * @param {number} currentChar currentChar to be parsed 26900 * @param {Object} fields the fields that have been extracted so far 26901 * @param {Object} regionSettings settings used to parse the rest of the number 26902 */ 26903 cic2: function(number, currentChar, fields, regionSettings) { 26904 return this.processField('cic', regionSettings.plan.getFieldLength('cic2'), number, currentChar, fields, regionSettings); 26905 }, 26906 /** 26907 * @private 26908 * @param {string} number phone number 26909 * @param {number} currentChar currentChar to be parsed 26910 * @param {Object} fields the fields that have been extracted so far 26911 * @param {Object} regionSettings settings used to parse the rest of the number 26912 */ 26913 cic3: function(number, currentChar, fields, regionSettings) { 26914 return this.processField('cic', regionSettings.plan.getFieldLength('cic3'), number, currentChar, fields, regionSettings); 26915 }, 26916 /** 26917 * @private 26918 * @param {string} number phone number 26919 * @param {number} currentChar currentChar to be parsed 26920 * @param {Object} fields the fields that have been extracted so far 26921 * @param {Object} regionSettings settings used to parse the rest of the number 26922 */ 26923 start: function(number, currentChar, fields, regionSettings) { 26924 // don't do anything except transition to the next state 26925 return { 26926 number: number 26927 }; 26928 }, 26929 /** 26930 * @private 26931 * @param {string} number phone number 26932 * @param {number} currentChar currentChar to be parsed 26933 * @param {Object} fields the fields that have been extracted so far 26934 * @param {Object} regionSettings settings used to parse the rest of the number 26935 */ 26936 local: function(number, currentChar, fields, regionSettings) { 26937 // in open dialling plans, we can tell that this number is a local subscriber number because it 26938 // starts with a digit that indicates as such 26939 this.processSubscriberNumber(number, fields, regionSettings); 26940 return { 26941 number: "" 26942 }; 26943 } 26944 }; 26945 26946 // context-sensitive handler 26947 /** 26948 * @class 26949 * @private 26950 * @extends PhoneHandler 26951 * @constructor 26952 */ 26953 var CSStateHandler = function () { 26954 return this; 26955 }; 26956 26957 CSStateHandler.prototype = new PhoneHandler(); 26958 CSStateHandler.prototype.special = function (number, currentChar, fields, regionSettings) { 26959 var ret; 26960 26961 // found a special area code that is both a node and a leaf. In 26962 // this state, we have found the leaf, so chop off the end 26963 // character to make it a leaf. 26964 if (number.charAt(0) === "0") { 26965 fields.trunkAccess = number.charAt(0); 26966 fields.areaCode = number.substring(1, currentChar); 26967 } else { 26968 fields.areaCode = number.substring(0, currentChar); 26969 } 26970 this.processSubscriberNumber(number.substring(currentChar), fields, regionSettings); 26971 26972 ret = { 26973 number: "" 26974 }; 26975 26976 return ret; 26977 }; 26978 26979 /** 26980 * @class 26981 * @private 26982 * @extends PhoneHandler 26983 * @constructor 26984 */ 26985 var USStateHandler = function () { 26986 return this; 26987 }; 26988 26989 USStateHandler.prototype = new PhoneHandler(); 26990 USStateHandler.prototype.vsc = function (number, currentChar, fields, regionSettings) { 26991 var ret; 26992 26993 // found a VSC code (ie. a "star code") 26994 fields.vsc = number; 26995 26996 // treat the rest of the number as if it were a completely new number 26997 ret = { 26998 number: "" 26999 }; 27000 27001 return ret; 27002 }; 27003 27004 /** 27005 * Creates a phone handler instance that is correct for the locale. Phone handlers are 27006 * used to handle parsing of the various fields in a phone number. 27007 * 27008 * @protected 27009 * @static 27010 * @return {PhoneHandler} the correct phone handler for the locale 27011 */ 27012 var PhoneHandlerFactory = function (locale, plan) { 27013 if (plan.getContextFree() !== undefined && typeof(plan.getContextFree()) === 'boolean' && plan.getContextFree() === false) { 27014 return new CSStateHandler(); 27015 } 27016 var region = (locale && locale.getRegion()) || "ZZ"; 27017 switch (region) { 27018 case 'US': 27019 return new USStateHandler(); 27020 27021 default: 27022 return new PhoneHandler(); 27023 } 27024 }; 27025 27026 27027 /*< PhoneNumber.js */ 27028 /* 27029 * phonenum.js - Represent a phone number. 27030 * 27031 * Copyright © 2014-2015, JEDLSoft 27032 * 27033 * Licensed under the Apache License, Version 2.0 (the "License"); 27034 * you may not use this file except in compliance with the License. 27035 * You may obtain a copy of the License at 27036 * 27037 * http://www.apache.org/licenses/LICENSE-2.0 27038 * 27039 * Unless required by applicable law or agreed to in writing, software 27040 * distributed under the License is distributed on an "AS IS" BASIS, 27041 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 27042 * 27043 * See the License for the specific language governing permissions and 27044 * limitations under the License. 27045 */ 27046 27047 /* 27048 !depends 27049 ilib.js 27050 NumberingPlan.js 27051 PhoneLocale.js 27052 PhoneHandlerFactory.js 27053 Utils.js 27054 JSUtils.js 27055 */ 27056 27057 // !data states idd mnc 27058 27059 27060 /** 27061 * @class 27062 * Create a new phone number instance that parses the phone number parameter for its 27063 * constituent parts, and store them as separate fields in the returned object. 27064 * 27065 * The options object may include any of these properties: 27066 * 27067 * <ul> 27068 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 27069 * numbering plan to use. 27070 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 27071 * currently connected to, if known. This also can give a clue as to which numbering plan to 27072 * use 27073 * <li>onLoad - a callback function to call when this instance is fully 27074 * loaded. When the onLoad option is given, this class will attempt to 27075 * load any missing locale data using the ilib loader callback. 27076 * When the constructor is done (even if the data is already preassembled), the 27077 * onLoad function is called with the current instance as a parameter, so this 27078 * callback can be used with preassembled or dynamic loading or a mix of the two. 27079 * <li>sync - tell whether to load any missing locale data synchronously or 27080 * asynchronously. If this option is given as "false", then the "onLoad" 27081 * callback must be given, as the instance returned from this constructor will 27082 * not be usable for a while. 27083 * <li><i>loadParams</i> - an object containing parameters to pass to the 27084 * loader callback function when locale data is missing. The parameters are not 27085 * interpretted or modified in any way. They are simply passed along. The object 27086 * may contain any property/value pairs as long as the calling code is in 27087 * agreement with the loader callback function as to what those parameters mean. 27088 * </ul> 27089 * 27090 * This function is locale-sensitive, and will assume any number passed to it is 27091 * appropriate for the given locale. If the MCC is given, this method will assume 27092 * that numbers without an explicit country code have been dialled within the country 27093 * given by the MCC. This affects how things like area codes are parsed. If the MCC 27094 * is not given, this method will use the given locale to determine the country 27095 * code. If the locale is not explicitly given either, then this function uses the 27096 * region of current locale as the default.<p> 27097 * 27098 * The input number may contain any formatting characters for the given locale. Each 27099 * field that is returned in the json object is a simple string of digits with 27100 * all formatting and whitespace characters removed.<p> 27101 * 27102 * The number is decomposed into its parts, regardless if the number 27103 * contains formatting characters. If a particular part cannot be extracted from given 27104 * number, the field will not be returned as a field in the object. If no fields can be 27105 * extracted from the number at all, then all digits found in the string will be 27106 * returned in the subscriberNumber field. If the number parameter contains no 27107 * digits, an empty object is returned.<p> 27108 * 27109 * This instance can contain any of the following fields after parsing is done: 27110 * 27111 * <ul> 27112 * <li>vsc - if this number starts with a VSC (Vertical Service Code, or "star code"), this field will contain the star and the code together 27113 * <li>iddPrefix - the prefix for international direct dialing. This can either be in the form of a plus character or the IDD access code for the given locale 27114 * <li>countryCode - if this number is an international direct dial number, this is the country code 27115 * <li>cic - for "dial-around" services (access to other carriers), this is the prefix used as the carrier identification code 27116 * <li>emergency - an emergency services number 27117 * <li>mobilePrefix - prefix that introduces a mobile phone number 27118 * <li>trunkAccess - trunk access code (long-distance access) 27119 * <li>serviceCode - like a geographic area code, but it is a required prefix for various services 27120 * <li>areaCode - geographic area codes 27121 * <li>subscriberNumber - the unique number of the person or company that pays for this phone line 27122 * <li>extension - in some countries, extensions are dialed directly without going through an operator or a voice prompt system. If the number includes an extension, it is given in this field. 27123 * <li>invalid - this property is added and set to true if the parser found that the number is invalid in the numbering plan for the country. This method will make its best effort at parsing, but any digits after the error will go into the subscriberNumber field 27124 * </ul> 27125 * 27126 * The following rules determine how the number is parsed: 27127 * 27128 * <ol> 27129 * <li>If the number starts with a character that is alphabetic instead of numeric, do 27130 * not parse the number at all. There is a good chance that it is not really a phone number. 27131 * In this case, an empty instance will be returned. 27132 * <li>If the phone number uses the plus notation or explicitly uses the international direct 27133 * dialing prefix for the given locale, then the country code is identified in 27134 * the number. The rules of given locale are used to parse the IDD prefix, and then the rules 27135 * of the country in the prefix are used to parse the rest of the number. 27136 * <li>If a country code is provided as an argument to the function call, use that country's 27137 * parsing rules for the number. This is intended for programs like a Contacts application that 27138 * know what the country is of the person that owns the phone number and can pass that on as 27139 * a hint. 27140 * <li>If the appropriate locale cannot be easily determined, default to using the rules 27141 * for the current user's region. 27142 * </ol> 27143 * 27144 * Example: parsing the number "+49 02101345345-78" will give the following properties in the 27145 * resulting phone number instance: 27146 * 27147 * <pre> 27148 * { 27149 * iddPrefix: "+", 27150 * countryCode: "49", 27151 * areaCode: "02101", 27152 * subscriberNumber: "345345", 27153 * extension: "78" 27154 * } 27155 * </pre> 27156 * 27157 * Note that in this example, because international direct dialing is explicitly used 27158 * in the number, the part of this number after the IDD prefix and country code will be 27159 * parsed exactly the same way in all locales with German rules (country code 49). 27160 * 27161 * Regions currently supported are: 27162 * 27163 * <ul> 27164 * <li>NANP (North American Numbering Plan) countries - USA, Canada, Bermuda, various Caribbean nations 27165 * <li>UK 27166 * <li>Republic of Ireland 27167 * <li>Germany 27168 * <li>France 27169 * <li>Spain 27170 * <li>Italy 27171 * <li>Mexico 27172 * <li>India 27173 * <li>People's Republic of China 27174 * <li>Netherlands 27175 * <li>Belgium 27176 * <li>Luxembourg 27177 * <li>Australia 27178 * <li>New Zealand 27179 * <li>Singapore 27180 * <li>Korea 27181 * <li>Japan 27182 * <li>Russia 27183 * <li>Brazil 27184 * </ul> 27185 * 27186 * @constructor 27187 * @param {!string|PhoneNumber} number A free-form phone number to be parsed, or another phone 27188 * number instance to copy 27189 * @param {Object=} options options that guide the parser in parsing the number 27190 */ 27191 var PhoneNumber = function(number, options) { 27192 var stateData, 27193 regionSettings; 27194 27195 this.sync = true; 27196 this.loadParams = {}; 27197 27198 27199 if (options) { 27200 if (typeof(options.sync) === 'boolean') { 27201 this.sync = options.sync; 27202 } 27203 27204 if (options.loadParams) { 27205 this.loadParams = options.loadParams; 27206 } 27207 27208 if (typeof(options.onLoad) === 'function') { 27209 /** 27210 * @private 27211 * @type {function(PhoneNumber)} 27212 */ 27213 this.onLoad = options.onLoad; 27214 } 27215 } else { 27216 options = {sync: true}; 27217 } 27218 27219 if (!number || (typeof number === "string" && number.length === 0)) { 27220 if (typeof(options.onLoad) === 'function') { 27221 options.onLoad(undefined); 27222 } 27223 27224 return this; 27225 } 27226 27227 if (typeof number === "object") { 27228 /** 27229 * The vertical service code. These are codes that typically 27230 * start with a star or hash, like "*69" for "dial back the 27231 * last number that called me". 27232 * @type {string|undefined} 27233 */ 27234 this.vsc = number.vsc; 27235 27236 /** 27237 * The international direct dialing prefix. This is always 27238 * followed by the country code. 27239 * @type {string} 27240 */ 27241 this.iddPrefix = number.iddPrefix; 27242 27243 /** 27244 * The unique IDD country code for the country where the 27245 * phone number is serviced. 27246 * @type {string|undefined} 27247 */ 27248 this.countryCode = number.countryCode; 27249 27250 /** 27251 * The digits required to access the trunk. 27252 * @type {string|undefined} 27253 */ 27254 this.trunkAccess = number.trunkAccess; 27255 27256 /** 27257 * The carrier identification code used to identify 27258 * alternate long distance or international carriers. 27259 * @type {string|undefined} 27260 */ 27261 this.cic = number.cic; 27262 27263 /** 27264 * Identifies an emergency number that is typically 27265 * short, such as "911" in North America or "112" in 27266 * many other places in the world. 27267 * @type {string|undefined} 27268 */ 27269 this.emergency = number.emergency; 27270 27271 /** 27272 * The prefix of the subscriber number that indicates 27273 * that this is the number of a mobile phone. 27274 * @type {string|undefined} 27275 */ 27276 this.mobilePrefix = number.mobilePrefix; 27277 27278 /** 27279 * The prefix that identifies this number as commercial 27280 * service number. 27281 * @type {string|undefined} 27282 */ 27283 this.serviceCode = number.serviceCode; 27284 27285 /** 27286 * The area code prefix of a land line number. 27287 * @type {string|undefined} 27288 */ 27289 this.areaCode = number.areaCode; 27290 27291 /** 27292 * The unique number associated with the subscriber 27293 * of this phone. 27294 * @type {string|undefined} 27295 */ 27296 this.subscriberNumber = number.subscriberNumber; 27297 27298 /** 27299 * The direct dial extension number. 27300 * @type {string|undefined} 27301 */ 27302 this.extension = number.extension; 27303 27304 /** 27305 * @private 27306 * @type {boolean} 27307 */ 27308 this.invalid = number.invalid; 27309 27310 if (number.plan && number.locale) { 27311 /** 27312 * @private 27313 * @type {NumberingPlan} 27314 */ 27315 this.plan = number.plan; 27316 27317 /** 27318 * @private 27319 * @type {PhoneLocale} 27320 */ 27321 this.locale = number.locale; 27322 27323 /** 27324 * @private 27325 * @type {NumberingPlan} 27326 */ 27327 this.destinationPlan = number.destinationPlan; 27328 27329 /** 27330 * @private 27331 * @type {PhoneLocale} 27332 */ 27333 this.destinationLocale = number.destinationLocale; 27334 27335 if (options && typeof(options.onLoad) === 'function') { 27336 options.onLoad(this); 27337 } 27338 return; 27339 } 27340 } 27341 27342 new PhoneLocale({ 27343 locale: options && options.locale, 27344 mcc: options && options.mcc, 27345 sync: this.sync, 27346 loadParams: this.loadParams, 27347 onLoad: ilib.bind(this, function(loc) { 27348 this.locale = this.destinationLocale = loc; 27349 new NumberingPlan({ 27350 locale: this.locale, 27351 sync: this.sync, 27352 loadParms: this.loadParams, 27353 onLoad: ilib.bind(this, function (plan) { 27354 this.plan = this.destinationPlan = plan; 27355 27356 if (typeof number === "object") { 27357 // the copy constructor code above did not find the locale 27358 // or plan before, but now they are loaded, so we can return 27359 // already without going further 27360 if (typeof(options.onLoad) === "function") { 27361 options.onLoad(this); 27362 } 27363 return; 27364 } 27365 Utils.loadData({ 27366 name: "states.json", 27367 object: "PhoneNumber", 27368 locale: this.locale, 27369 sync: this.sync, 27370 loadParams: JSUtils.merge(this.loadParams, { 27371 returnOne: true 27372 }), 27373 callback: ilib.bind(this, function (stdata) { 27374 if (!stdata) { 27375 stdata = PhoneNumber._defaultStates; 27376 } 27377 27378 stateData = stdata; 27379 27380 regionSettings = { 27381 stateData: stateData, 27382 plan: plan, 27383 handler: PhoneHandlerFactory(this.locale, plan) 27384 }; 27385 27386 // use ^ to indicate the beginning of the number, because certain things only match at the beginning 27387 number = "^" + number.replace(/\^/g, ''); 27388 number = PhoneNumber._stripFormatting(number); 27389 27390 this._parseNumber(number, regionSettings, options); 27391 }) 27392 }); 27393 }) 27394 }); 27395 }) 27396 }); 27397 }; 27398 27399 /** 27400 * Parse an International Mobile Subscriber Identity (IMSI) number into its 3 constituent parts: 27401 * 27402 * <ol> 27403 * <li>mcc - Mobile Country Code, which identifies the country where the phone is currently receiving 27404 * service. 27405 * <li>mnc - Mobile Network Code, which identifies the carrier which is currently providing service to the phone 27406 * <li>msin - Mobile Subscription Identifier Number. This is a unique number identifying the mobile phone on 27407 * the network, which usually maps to an account/subscriber in the carrier's database. 27408 * </ol> 27409 * 27410 * Because this function may need to load data to identify the above parts, you can pass an options 27411 * object that controls how the data is loaded. The options may contain any of the following properties: 27412 * 27413 * <ul> 27414 * <li>onLoad - a callback function to call when the parsing is done. When the onLoad option is given, 27415 * this method will attempt to load the locale data using the ilib loader callback. When it is done 27416 * (even if the data is already preassembled), the onLoad function is called with the parsing results 27417 * as a parameter, so this callback can be used with preassembled or dynamic, synchronous or 27418 * asynchronous loading or a mix of the above. 27419 * <li>sync - tell whether to load any missing locale data synchronously or asynchronously. If this 27420 * option is given as "false", then the "onLoad" callback must be given, as the results returned from 27421 * this constructor will not be usable for a while. 27422 * <li><i>loadParams</i> - an object containing parameters to pass to the loader callback function 27423 * when locale data is missing. The parameters are not interpretted or modified in any way. They are 27424 * simply passed along. The object may contain any property/value pairs as long as the calling code is in 27425 * agreement with the loader callback function as to what those parameters mean. 27426 * </ul> 27427 * 27428 * @static 27429 * @param {string} imsi IMSI number to parse 27430 * @param {Object} options options controlling the loading of the locale data 27431 * @return {{mcc:string,mnc:string,msin:string}|undefined} components of the IMSI number, when the locale data 27432 * is loaded synchronously, or undefined if asynchronous 27433 */ 27434 PhoneNumber.parseImsi = function(imsi, options) { 27435 var sync = true, 27436 loadParams = {}, 27437 fields = {}; 27438 27439 if (!imsi) { 27440 if (options && typeof(options.onLoad) === 'function') { 27441 options.onLoad(undefined); 27442 } 27443 return undefined; 27444 } 27445 27446 if (options) { 27447 if (typeof(options.sync) !== 'undefined') { 27448 sync = !!options.sync; 27449 } 27450 27451 if (options.loadParams) { 27452 loadParams = options.loadParams; 27453 } 27454 } 27455 27456 if (ilib.data.mnc) { 27457 fields = PhoneNumber._parseImsi(ilib.data.mnc, imsi); 27458 27459 if (options && typeof(options.onLoad) === 'function') { 27460 options.onLoad(fields); 27461 } 27462 } else { 27463 Utils.loadData({ 27464 name: "mnc.json", 27465 object: "PhoneNumber", 27466 nonlocale: true, 27467 sync: sync, 27468 loadParams: loadParams, 27469 callback: ilib.bind(this, function(data) { 27470 ilib.data.mnc = data; 27471 fields = PhoneNumber._parseImsi(data, imsi); 27472 27473 if (options && typeof(options.onLoad) === 'function') { 27474 options.onLoad(fields); 27475 } 27476 }) 27477 }); 27478 } 27479 return fields; 27480 }; 27481 27482 27483 /** 27484 * @static 27485 * @protected 27486 */ 27487 PhoneNumber._parseImsi = function(data, imsi) { 27488 var ch, 27489 i, 27490 currentState, 27491 end, 27492 handlerMethod, 27493 newState, 27494 fields = {}, 27495 lastLeaf, 27496 consumed = 0; 27497 27498 currentState = data; 27499 if (!currentState) { 27500 // can't parse anything 27501 return undefined; 27502 } 27503 27504 i = 0; 27505 while (i < imsi.length) { 27506 ch = PhoneNumber._getCharacterCode(imsi.charAt(i)); 27507 // console.info("parsing char " + imsi.charAt(i) + " code: " + ch); 27508 if (ch >= 0) { 27509 newState = currentState.s && currentState.s[ch]; 27510 27511 if (typeof(newState) === 'object') { 27512 if (typeof(newState.l) !== 'undefined') { 27513 // save for latter if needed 27514 lastLeaf = newState; 27515 consumed = i; 27516 } 27517 // console.info("recognized digit " + ch + " continuing..."); 27518 // recognized digit, so continue parsing 27519 currentState = newState; 27520 i++; 27521 } else { 27522 if ((typeof(newState) === 'undefined' || newState === 0 || 27523 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 27524 lastLeaf) { 27525 // this is possibly a look-ahead and it didn't work... 27526 // so fall back to the last leaf and use that as the 27527 // final state 27528 newState = lastLeaf; 27529 i = consumed; 27530 } 27531 27532 if ((typeof(newState) === 'number' && newState) || 27533 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 27534 // final state 27535 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 27536 handlerMethod = PhoneNumber._states[stateNumber]; 27537 27538 // console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 27539 27540 // deal with syntactic ambiguity by using the "special" end state instead of "area" 27541 if ( handlerMethod === "area" ) { 27542 end = i+1; 27543 } else { 27544 // unrecognized imsi, so just assume the mnc is 3 digits 27545 end = 6; 27546 } 27547 27548 fields.mcc = imsi.substring(0,3); 27549 fields.mnc = imsi.substring(3,end); 27550 fields.msin = imsi.substring(end); 27551 27552 return fields; 27553 } else { 27554 // parse error 27555 if (imsi.length >= 6) { 27556 fields.mcc = imsi.substring(0,3); 27557 fields.mnc = imsi.substring(3,6); 27558 fields.msin = imsi.substring(6); 27559 } 27560 return fields; 27561 } 27562 } 27563 } else if (ch === -1) { 27564 // non-transition character, continue parsing in the same state 27565 i++; 27566 } else { 27567 // should not happen 27568 // console.info("skipping character " + ch); 27569 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 27570 i++; 27571 } 27572 } 27573 27574 if (i >= imsi.length && imsi.length >= 6) { 27575 // we reached the end of the imsi, but did not finish recognizing anything. 27576 // Default to last resort and assume 3 digit mnc 27577 fields.mcc = imsi.substring(0,3); 27578 fields.mnc = imsi.substring(3,6); 27579 fields.msin = imsi.substring(6); 27580 } else { 27581 // unknown or not enough characters for a real imsi 27582 fields = undefined; 27583 } 27584 27585 // console.info("Globalization.Phone.parseImsi: final result is: " + JSON.stringify(fields)); 27586 return fields; 27587 }; 27588 27589 /** 27590 * @static 27591 * @private 27592 */ 27593 PhoneNumber._stripFormatting = function(str) { 27594 var ret = ""; 27595 var i; 27596 27597 for (i = 0; i < str.length; i++) { 27598 if (PhoneNumber._getCharacterCode(str.charAt(i)) >= -1) { 27599 ret += str.charAt(i); 27600 } 27601 } 27602 return ret; 27603 }; 27604 27605 /** 27606 * @static 27607 * @protected 27608 */ 27609 PhoneNumber._getCharacterCode = function(ch) { 27610 if (ch >= '0' && ch <= '9') { 27611 return ch - '0'; 27612 } 27613 switch (ch) { 27614 case '+': 27615 return 10; 27616 case '*': 27617 return 11; 27618 case '#': 27619 return 12; 27620 case '^': 27621 return 13; 27622 case 'p': // pause chars 27623 case 'P': 27624 case 't': 27625 case 'T': 27626 case 'w': 27627 case 'W': 27628 return -1; 27629 case 'x': 27630 case 'X': 27631 case ',': 27632 case ';': // extension char 27633 return -1; 27634 } 27635 return -2; 27636 }; 27637 27638 /** 27639 * @private 27640 */ 27641 PhoneNumber._states = [ 27642 "none", 27643 "unknown", 27644 "plus", 27645 "idd", 27646 "cic", 27647 "service", 27648 "cell", 27649 "area", 27650 "vsc", 27651 "country", 27652 "personal", 27653 "special", 27654 "trunk", 27655 "premium", 27656 "emergency", 27657 "service2", 27658 "service3", 27659 "service4", 27660 "cic2", 27661 "cic3", 27662 "start", 27663 "local" 27664 ]; 27665 27666 /** 27667 * @private 27668 */ 27669 PhoneNumber._fieldOrder = [ 27670 "vsc", 27671 "iddPrefix", 27672 "countryCode", 27673 "trunkAccess", 27674 "cic", 27675 "emergency", 27676 "mobilePrefix", 27677 "serviceCode", 27678 "areaCode", 27679 "subscriberNumber", 27680 "extension" 27681 ]; 27682 27683 PhoneNumber._defaultStates = { 27684 "s": [ 27685 0, 27686 21, // 1 -> local 27687 21, // 2 -> local 27688 21, // 3 -> local 27689 21, // 4 -> local 27690 21, // 5 -> local 27691 21, // 6 -> local 27692 21, // 7 -> local 27693 21, // 8 -> local 27694 21, // 9 -> local 27695 0,0,0, 27696 { // ^ 27697 "s": [ 27698 { // ^0 27699 "s": [3], // ^00 -> idd 27700 "l": 12 // ^0 -> trunk 27701 }, 27702 21, // ^1 -> local 27703 21, // ^2 -> local 27704 21, // ^3 -> local 27705 21, // ^4 -> local 27706 21, // ^5 -> local 27707 21, // ^6 -> local 27708 21, // ^7 -> local 27709 21, // ^8 -> local 27710 21, // ^9 -> local 27711 2 // ^+ -> plus 27712 ] 27713 } 27714 ] 27715 }; 27716 27717 PhoneNumber.prototype = { 27718 /** 27719 * @protected 27720 * @param {string} number 27721 * @param {Object} regionData 27722 * @param {Object} options 27723 * @param {string} countryCode 27724 */ 27725 _parseOtherCountry: function(number, regionData, options, countryCode) { 27726 new PhoneLocale({ 27727 locale: this.locale, 27728 countryCode: countryCode, 27729 sync: this.sync, 27730 loadParms: this.loadParams, 27731 onLoad: ilib.bind(this, function (loc) { 27732 /* 27733 * this.locale is the locale where this number is being parsed, 27734 * and is used to parse the IDD prefix, if any, and this.destinationLocale is 27735 * the locale of the rest of this number after the IDD prefix. 27736 */ 27737 /** @type {PhoneLocale} */ 27738 this.destinationLocale = loc; 27739 27740 Utils.loadData({ 27741 name: "states.json", 27742 object: "PhoneNumber", 27743 locale: this.destinationLocale, 27744 sync: this.sync, 27745 loadParams: JSUtils.merge(this.loadParams, { 27746 returnOne: true 27747 }), 27748 callback: ilib.bind(this, function (stateData) { 27749 if (!stateData) { 27750 stateData = PhoneNumber._defaultStates; 27751 } 27752 27753 new NumberingPlan({ 27754 locale: this.destinationLocale, 27755 sync: this.sync, 27756 loadParms: this.loadParams, 27757 onLoad: ilib.bind(this, function (plan) { 27758 /* 27759 * this.plan is the plan where this number is being parsed, 27760 * and is used to parse the IDD prefix, if any, and this.destinationPlan is 27761 * the plan of the rest of this number after the IDD prefix in the 27762 * destination locale. 27763 */ 27764 /** @type {NumberingPlan} */ 27765 this.destinationPlan = plan; 27766 27767 var regionSettings = { 27768 stateData: stateData, 27769 plan: plan, 27770 handler: PhoneHandlerFactory(this.destinationLocale, plan) 27771 }; 27772 27773 // for plans that do not skip the trunk code when dialing from 27774 // abroad, we need to treat the number from here on in as if it 27775 // were parsing a local number from scratch. That way, the parser 27776 // does not get confused between parts of the number at the 27777 // beginning of the number, and parts in the middle. 27778 if (!plan.getSkipTrunk()) { 27779 number = '^' + number; 27780 } 27781 27782 // recursively call the parser with the new states data 27783 // to finish the parsing 27784 this._parseNumber(number, regionSettings, options); 27785 }) 27786 }); 27787 }) 27788 }); 27789 }) 27790 }); 27791 }, 27792 27793 /** 27794 * @protected 27795 * @param {string} number 27796 * @param {Object} regionData 27797 * @param {Object} options 27798 */ 27799 _parseNumber: function(number, regionData, options) { 27800 var i, ch, 27801 regionSettings, 27802 newState, 27803 dot, 27804 handlerMethod, 27805 result, 27806 currentState = regionData.stateData, 27807 lastLeaf = undefined, 27808 consumed = 0; 27809 27810 regionSettings = regionData; 27811 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 27812 27813 i = 0; 27814 while (i < number.length) { 27815 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 27816 if (ch >= 0) { 27817 // newState = stateData.states[state][ch]; 27818 newState = currentState.s && currentState.s[ch]; 27819 27820 if (!newState && currentState.s && currentState.s[dot]) { 27821 newState = currentState.s[dot]; 27822 } 27823 27824 if (typeof(newState) === 'object' && i+1 < number.length) { 27825 if (typeof(newState.l) !== 'undefined') { 27826 // this is a leaf node, so save that for later if needed 27827 lastLeaf = newState; 27828 consumed = i; 27829 } 27830 // console.info("recognized digit " + ch + " continuing..."); 27831 // recognized digit, so continue parsing 27832 currentState = newState; 27833 i++; 27834 } else { 27835 if ((typeof(newState) === 'undefined' || newState === 0 || 27836 (typeof(newState) === 'object' && typeof(newState.l) === 'undefined')) && 27837 lastLeaf) { 27838 // this is possibly a look-ahead and it didn't work... 27839 // so fall back to the last leaf and use that as the 27840 // final state 27841 newState = lastLeaf; 27842 i = consumed; 27843 consumed = 0; 27844 lastLeaf = undefined; 27845 } 27846 27847 if ((typeof(newState) === 'number' && newState) || 27848 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 27849 // final state 27850 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 27851 handlerMethod = PhoneNumber._states[stateNumber]; 27852 27853 if (number.charAt(0) === '^') { 27854 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 27855 } else { 27856 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 27857 } 27858 27859 // reparse whatever is left 27860 number = result.number; 27861 i = consumed = 0; 27862 lastLeaf = undefined; 27863 27864 //console.log("reparsing with new number: " + number); 27865 currentState = regionSettings.stateData; 27866 // if the handler requested a special sub-table, use it for this round of parsing, 27867 // otherwise, set it back to the regular table to continue parsing 27868 27869 if (result.countryCode !== undefined) { 27870 this._parseOtherCountry(number, regionData, options, result.countryCode); 27871 // don't process any further -- let the work be done in the onLoad callbacks 27872 return; 27873 } else if (result.table !== undefined) { 27874 Utils.loadData({ 27875 name: result.table + ".json", 27876 object: "PhoneNumber", 27877 nonlocale: true, 27878 sync: this.sync, 27879 loadParams: this.loadParams, 27880 callback: ilib.bind(this, function (data) { 27881 if (!data) { 27882 data = PhoneNumber._defaultStates; 27883 } 27884 27885 regionSettings = { 27886 stateData: data, 27887 plan: regionSettings.plan, 27888 handler: regionSettings.handler 27889 }; 27890 27891 // recursively call the parser with the new states data 27892 // to finish the parsing 27893 this._parseNumber(number, regionSettings, options); 27894 }) 27895 }); 27896 // don't process any further -- let the work be done in the onLoad callbacks 27897 return; 27898 } else if (result.skipTrunk !== undefined) { 27899 ch = PhoneNumber._getCharacterCode(regionSettings.plan.getTrunkCode()); 27900 currentState = currentState.s && currentState.s[ch]; 27901 } 27902 } else { 27903 handlerMethod = (typeof(newState) === 'number') ? "none" : "local"; 27904 // failed parse. Either no last leaf to fall back to, or there was an explicit 27905 // zero in the table. Put everything else in the subscriberNumber field as the 27906 // default place 27907 if (number.charAt(0) === '^') { 27908 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 27909 } else { 27910 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 27911 } 27912 break; 27913 } 27914 } 27915 } else if (ch === -1) { 27916 // non-transition character, continue parsing in the same state 27917 i++; 27918 } else { 27919 // should not happen 27920 // console.info("skipping character " + ch); 27921 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 27922 i++; 27923 } 27924 } 27925 if (i >= number.length && currentState !== regionData.stateData) { 27926 handlerMethod = (typeof(currentState.l) === 'undefined' || currentState === 0) ? "none" : "local"; 27927 // we reached the end of the phone number, but did not finish recognizing anything. 27928 // Default to last resort and put everything that is left into the subscriber number 27929 //console.log("Reached end of number before parsing was complete. Using handler for method none.") 27930 if (number.charAt(0) === '^') { 27931 result = regionSettings.handler[handlerMethod](number.slice(1), i-1, this, regionSettings); 27932 } else { 27933 result = regionSettings.handler[handlerMethod](number, i, this, regionSettings); 27934 } 27935 } 27936 27937 // let the caller know we are done parsing 27938 if (this.onLoad) { 27939 this.onLoad(this); 27940 } 27941 }, 27942 /** 27943 * @protected 27944 */ 27945 _getPrefix: function() { 27946 return this.areaCode || this.serviceCode || this.mobilePrefix || ""; 27947 }, 27948 27949 /** 27950 * @protected 27951 */ 27952 _hasPrefix: function() { 27953 return (this._getPrefix() !== ""); 27954 }, 27955 27956 /** 27957 * Exclusive or -- return true, if one is defined and the other isn't 27958 * @protected 27959 */ 27960 _xor : function(left, right) { 27961 if ((left === undefined && right === undefined ) || (left !== undefined && right !== undefined)) { 27962 return false; 27963 } else { 27964 return true; 27965 } 27966 }, 27967 27968 /** 27969 * return a version of the phone number that contains only the dialable digits in the correct order 27970 * @protected 27971 */ 27972 _join: function () { 27973 var fieldName, formatted = ""; 27974 27975 try { 27976 for (var field in PhoneNumber._fieldOrder) { 27977 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string') { 27978 fieldName = PhoneNumber._fieldOrder[field]; 27979 // console.info("normalize: formatting field " + fieldName); 27980 if (this[fieldName] !== undefined) { 27981 formatted += this[fieldName]; 27982 } 27983 } 27984 } 27985 } catch ( e ) { 27986 //console.warn("caught exception in _join: " + e); 27987 throw e; 27988 } 27989 return formatted; 27990 }, 27991 27992 /** 27993 * This routine will compare the two phone numbers in an locale-sensitive 27994 * manner to see if they possibly reference the same phone number.<p> 27995 * 27996 * In many places, 27997 * there are multiple ways to reach the same phone number. In North America for 27998 * example, you might have a number with the trunk access code of "1" and another 27999 * without, and they reference the exact same phone number. This is considered a 28000 * strong match. For a different pair of numbers, one may be a local number and 28001 * the other a full phone number with area code, which may reference the same 28002 * phone number if the local number happens to be located in that area code. 28003 * However, you cannot say for sure if it is in that area code, so it will 28004 * be considered a somewhat weaker match.<p> 28005 * 28006 * Similarly, in other countries, there are sometimes different ways of 28007 * reaching the same destination, and the way that numbers 28008 * match depends on the locale.<p> 28009 * 28010 * The various phone number fields are handled differently for matches. There 28011 * are various fields that do not need to match at all. For example, you may 28012 * type equally enter "00" or "+" into your phone to start international direct 28013 * dialling, so the iddPrefix field does not need to match at all.<p> 28014 * 28015 * Typically, fields that require matches need to match exactly if both sides have a value 28016 * for that field. If both sides specify a value and those values differ, that is 28017 * a strong non-match. If one side does not have a value and the other does, that 28018 * causes a partial match, because the number with the missing field may possibly 28019 * have an implied value that matches the other number. For example, the numbers 28020 * "650-555-1234" and "555-1234" have a partial match as the local number "555-1234" 28021 * might possibly have the same 650 area code as the first number, and might possibly 28022 * not. If both side do not specify a value for a particular field, that field is 28023 * considered matching.<p> 28024 * 28025 * The values of following fields are ignored when performing matches: 28026 * 28027 * <ul> 28028 * <li>vsc 28029 * <li>iddPrefix 28030 * <li>cic 28031 * <li>trunkAccess 28032 * </ul> 28033 * 28034 * The values of the following fields matter if they do not match: 28035 * 28036 * <ul> 28037 * <li>countryCode - A difference causes a moderately strong problem except for 28038 * certain countries where there is a way to access the same subscriber via IDD 28039 * and via intranetwork dialling 28040 * <li>mobilePrefix - A difference causes a possible non-match 28041 * <li>serviceCode - A difference causes a possible non-match 28042 * <li>areaCode - A difference causes a possible non-match 28043 * <li>subscriberNumber - A difference causes a very strong non-match 28044 * <li>extension - A difference causes a minor non-match 28045 * </ul> 28046 * 28047 * @param {string|PhoneNumber} other other phone number to compare this one to 28048 * @return {number} non-negative integer describing the percentage quality of the 28049 * match. 100 means a very strong match (100%), and lower numbers are less and 28050 * less strong, down to 0 meaning not at all a match. 28051 */ 28052 compare: function (other) { 28053 var match = 100, 28054 FRdepartments = {"590":1, "594":1, "596":1, "262":1}, 28055 ITcountries = {"378":1, "379":1}, 28056 thisPrefix, 28057 otherPrefix, 28058 currentCountryCode = 0; 28059 28060 if (typeof this.locale.region === "string") { 28061 currentCountryCode = this.locale._mapRegiontoCC(this.locale.region); 28062 } 28063 28064 // subscriber number must be present and must match 28065 if (!this.subscriberNumber || !other.subscriberNumber || this.subscriberNumber !== other.subscriberNumber) { 28066 return 0; 28067 } 28068 28069 // extension must match if it is present 28070 if (this._xor(this.extension, other.extension) || this.extension !== other.extension) { 28071 return 0; 28072 } 28073 28074 if (this._xor(this.countryCode, other.countryCode)) { 28075 // if one doesn't have a country code, give it some demerit points, but if the 28076 // one that has the country code has something other than the current country 28077 // add even more. Ignore the special cases where you can dial the same number internationally or via 28078 // the local numbering system 28079 switch (this.locale.getRegion()) { 28080 case 'FR': 28081 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 28082 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 28083 match -= 100; 28084 } 28085 } else { 28086 match -= 16; 28087 } 28088 break; 28089 case 'IT': 28090 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 28091 if (this.areaCode !== other.areaCode) { 28092 match -= 100; 28093 } 28094 } else { 28095 match -= 16; 28096 } 28097 break; 28098 default: 28099 match -= 16; 28100 if ((this.countryCode !== undefined && this.countryCode !== currentCountryCode) || 28101 (other.countryCode !== undefined && other.countryCode !== currentCountryCode)) { 28102 match -= 16; 28103 } 28104 } 28105 } else if (this.countryCode !== other.countryCode) { 28106 // ignore the special cases where you can dial the same number internationally or via 28107 // the local numbering system 28108 if (other.countryCode === '33' || this.countryCode === '33') { 28109 // france 28110 if (this.countryCode in FRdepartments || other.countryCode in FRdepartments) { 28111 if (this.areaCode !== other.areaCode || this.mobilePrefix !== other.mobilePrefix) { 28112 match -= 100; 28113 } 28114 } else { 28115 match -= 100; 28116 } 28117 } else if (this.countryCode === '39' || other.countryCode === '39') { 28118 // italy 28119 if (this.countryCode in ITcountries || other.countryCode in ITcountries) { 28120 if (this.areaCode !== other.areaCode) { 28121 match -= 100; 28122 } 28123 } else { 28124 match -= 100; 28125 } 28126 } else { 28127 match -= 100; 28128 } 28129 } 28130 28131 if (this._xor(this.serviceCode, other.serviceCode)) { 28132 match -= 20; 28133 } else if (this.serviceCode !== other.serviceCode) { 28134 match -= 100; 28135 } 28136 28137 if (this._xor(this.mobilePrefix, other.mobilePrefix)) { 28138 match -= 20; 28139 } else if (this.mobilePrefix !== other.mobilePrefix) { 28140 match -= 100; 28141 } 28142 28143 if (this._xor(this.areaCode, other.areaCode)) { 28144 // one has an area code, the other doesn't, so dock some points. It could be a match if the local 28145 // number in the one number has the same implied area code as the explicit area code in the other number. 28146 match -= 12; 28147 } else if (this.areaCode !== other.areaCode) { 28148 match -= 100; 28149 } 28150 28151 thisPrefix = this._getPrefix(); 28152 otherPrefix = other._getPrefix(); 28153 28154 if (thisPrefix && otherPrefix && thisPrefix !== otherPrefix) { 28155 match -= 100; 28156 } 28157 28158 // make sure we are between 0 and 100 28159 if (match < 0) { 28160 match = 0; 28161 } else if (match > 100) { 28162 match = 100; 28163 } 28164 28165 return match; 28166 }, 28167 28168 /** 28169 * Determine whether or not the other phone number is exactly equal to the current one.<p> 28170 * 28171 * The difference between the compare method and the equals method is that the compare 28172 * method compares normalized numbers with each other and returns the degree of match, 28173 * whereas the equals operator returns true iff the two numbers contain the same fields 28174 * and the fields are exactly the same. Functions and other non-phone number properties 28175 * are not compared. 28176 * @param {string|PhoneNumber} other another phone number to compare to this one 28177 * @return {boolean} true if the numbers are the same, false otherwise 28178 */ 28179 equals: function equals(other) { 28180 if (other.locale && this.locale && !this.locale.equals(other.locale) && (!this.countryCode || !other.countryCode)) { 28181 return false; 28182 } 28183 28184 var _this = this; 28185 return PhoneNumber._fieldOrder.every(function(field) { 28186 return _this[field] === other[field]; 28187 }); 28188 }, 28189 28190 /** 28191 * @private 28192 * @param {{ 28193 * mcc:string, 28194 * defaultAreaCode:string, 28195 * country:string, 28196 * networkType:string, 28197 * assistedDialing:boolean, 28198 * sms:boolean, 28199 * manualDialing:boolean 28200 * }} options an object containing options to help in normalizing. 28201 * @param {PhoneNumber} norm 28202 * @param {PhoneLocale} homeLocale 28203 * @param {PhoneLocale} currentLocale 28204 * @param {NumberingPlan} currentPlan 28205 * @param {PhoneLocale} destinationLocale 28206 * @param {NumberingPlan} destinationPlan 28207 * @param {boolean} sync 28208 * @param {Object|undefined} loadParams 28209 */ 28210 _doNormalize: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams) { 28211 var formatted = ""; 28212 28213 if (!norm.invalid && options && options.assistedDialing) { 28214 // don't normalize things that don't have subscriber numbers. Also, don't normalize 28215 // manually dialed local numbers. Do normalize local numbers in contact entries. 28216 if (norm.subscriberNumber && 28217 (!options.manualDialing || 28218 norm.iddPrefix || 28219 norm.countryCode || 28220 norm.trunkAccess)) { 28221 // console.log("normalize: assisted dialling normalization of " + JSON.stringify(norm)); 28222 if (currentLocale.getRegion() !== destinationLocale.getRegion()) { 28223 // we are currently calling internationally 28224 if (!norm._hasPrefix() && 28225 options.defaultAreaCode && 28226 destinationLocale.getRegion() === homeLocale.getRegion() && 28227 (!destinationPlan.getFieldLength("minLocalLength") || 28228 norm.subscriberNumber.length >= destinationPlan.getFieldLength("minLocalLength"))) { 28229 // area code is required when dialling from international, but only add it if we are dialing 28230 // to our home area. Otherwise, the default area code is not valid! 28231 norm.areaCode = options.defaultAreaCode; 28232 if (!destinationPlan.getSkipTrunk() && destinationPlan.getTrunkCode()) { 28233 // some phone systems require the trunk access code, even when dialling from international 28234 norm.trunkAccess = destinationPlan.getTrunkCode(); 28235 } 28236 } 28237 28238 if (norm.trunkAccess && destinationPlan.getSkipTrunk()) { 28239 // on some phone systems, the trunk access code is dropped when dialling from international 28240 delete norm.trunkAccess; 28241 } 28242 28243 // make sure to get the country code for the destination region, not the current region! 28244 if (options.sms) { 28245 if (homeLocale.getRegion() === "US" && currentLocale.getRegion() !== "US") { 28246 if (destinationLocale.getRegion() !== "US") { 28247 norm.iddPrefix = "011"; // non-standard code to make it go through the US first 28248 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 28249 } else if (options.networkType === "cdma") { 28250 delete norm.iddPrefix; 28251 delete norm.countryCode; 28252 if (norm.areaCode) { 28253 norm.trunkAccess = "1"; 28254 } 28255 } else if (norm.areaCode) { 28256 norm.iddPrefix = "+"; 28257 norm.countryCode = "1"; 28258 delete norm.trunkAccess; 28259 } 28260 } else { 28261 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 28262 norm.countryCode = norm.countryCode || homeLocale._mapRegiontoCC(destinationLocale.region); 28263 } 28264 } else if (norm._hasPrefix() && !norm.countryCode) { 28265 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.region); 28266 } 28267 28268 if (norm.countryCode && !options.sms) { 28269 // for CDMA, make sure to get the international dialling access code for the current region, not the destination region 28270 // all umts carriers support plus dialing 28271 norm.iddPrefix = (options.networkType === "cdma") ? currentPlan.getIDDCode() : "+"; 28272 } 28273 } else { 28274 // console.log("normalize: dialing within the country"); 28275 if (options.defaultAreaCode) { 28276 if (destinationPlan.getPlanStyle() === "open") { 28277 if (!norm.trunkAccess && norm._hasPrefix() && destinationPlan.getTrunkCode()) { 28278 // call is not local to this area code, so you have to dial the trunk code and the area code 28279 norm.trunkAccess = destinationPlan.getTrunkCode(); 28280 } 28281 } else { 28282 // In closed plans, you always have to dial the area code, even if the call is local. 28283 if (!norm._hasPrefix()) { 28284 if (destinationLocale.getRegion() === homeLocale.getRegion()) { 28285 norm.areaCode = options.defaultAreaCode; 28286 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 28287 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 28288 } 28289 } 28290 } else { 28291 if (destinationPlan.getTrunkRequired() && destinationPlan.getTrunkCode()) { 28292 norm.trunkAccess = norm.trunkAccess || destinationPlan.getTrunkCode(); 28293 } 28294 } 28295 } 28296 } 28297 28298 if (options.sms && 28299 homeLocale.getRegion() === "US" && 28300 currentLocale.getRegion() !== "US") { 28301 norm.iddPrefix = "011"; // make it go through the US first 28302 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 28303 delete norm.trunkAccess; 28304 } 28305 } else if (norm.iddPrefix || norm.countryCode) { 28306 // we are in our destination country, so strip the international dialling prefixes 28307 delete norm.iddPrefix; 28308 delete norm.countryCode; 28309 28310 if ((destinationPlan.getPlanStyle() === "open" || destinationPlan.getTrunkRequired()) && destinationPlan.getTrunkCode()) { 28311 norm.trunkAccess = destinationPlan.getTrunkCode(); 28312 } 28313 } 28314 } 28315 } 28316 } else if (!norm.invalid) { 28317 // console.log("normalize: non-assisted normalization"); 28318 if (!norm._hasPrefix() && options && options.defaultAreaCode && destinationLocale.getRegion() === homeLocale.region) { 28319 norm.areaCode = options.defaultAreaCode; 28320 } 28321 28322 if (!norm.countryCode && norm._hasPrefix()) { 28323 norm.countryCode = homeLocale._mapRegiontoCC(destinationLocale.getRegion()); 28324 } 28325 28326 if (norm.countryCode) { 28327 if (options && options.networkType && options.networkType === "cdma") { 28328 norm.iddPrefix = currentPlan.getIDDCode(); 28329 } else { 28330 // all umts carriers support plus dialing 28331 norm.iddPrefix = "+"; 28332 } 28333 28334 if (destinationPlan.getSkipTrunk() && norm.trunkAccess) { 28335 delete norm.trunkAccess; 28336 } else if (!destinationPlan.getSkipTrunk() && !norm.trunkAccess && destinationPlan.getTrunkCode()) { 28337 norm.trunkAccess = destinationPlan.getTrunkCode(); 28338 } 28339 } 28340 } 28341 28342 // console.info("normalize: after normalization, the normalized phone number is: " + JSON.stringify(norm)); 28343 formatted = norm._join(); 28344 28345 return formatted; 28346 }, 28347 28348 /** 28349 * @private 28350 * @param {{ 28351 * mcc:string, 28352 * defaultAreaCode:string, 28353 * country:string, 28354 * networkType:string, 28355 * assistedDialing:boolean, 28356 * sms:boolean, 28357 * manualDialing:boolean 28358 * }} options an object containing options to help in normalizing. 28359 * @param {PhoneNumber} norm 28360 * @param {PhoneLocale} homeLocale 28361 * @param {PhoneLocale} currentLocale 28362 * @param {NumberingPlan} currentPlan 28363 * @param {PhoneLocale} destinationLocale 28364 * @param {NumberingPlan} destinationPlan 28365 * @param {boolean} sync 28366 * @param {Object|undefined} loadParams 28367 * @param {function(string)} callback 28368 */ 28369 _doReparse: function(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams, callback) { 28370 var formatted, 28371 tempRegion; 28372 28373 if (options && 28374 options.assistedDialing && 28375 !norm.trunkAccess && 28376 !norm.iddPrefix && 28377 norm.subscriberNumber && 28378 norm.subscriberNumber.length > destinationPlan.getFieldLength("maxLocalLength")) { 28379 28380 // numbers that are too long are sometimes international direct dialed numbers that 28381 // are missing the IDD prefix. So, try reparsing it using a plus in front to see if that works. 28382 new PhoneNumber("+" + this._join(), { 28383 locale: this.locale, 28384 sync: sync, 28385 loadParms: loadParams, 28386 onLoad: ilib.bind(this, function (data) { 28387 tempRegion = (data.countryCode && data.locale._mapCCtoRegion(data.countryCode)); 28388 28389 if (tempRegion && tempRegion !== "unknown" && tempRegion !== "SG") { 28390 // only use it if it is a recognized country code. Singapore (SG) is a special case. 28391 norm = data; 28392 destinationLocale = data.destinationLocale; 28393 destinationPlan = data.destinationPlan; 28394 } 28395 28396 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 28397 if (typeof(callback) === 'function') { 28398 callback(formatted); 28399 } 28400 }) 28401 }); 28402 } else if (options && options.assistedDialing && norm.invalid && currentLocale.region !== norm.locale.region) { 28403 // if this number is not valid for the locale it was parsed with, try again with the current locale 28404 // console.log("norm is invalid. Attempting to reparse with the current locale"); 28405 28406 new PhoneNumber(this._join(), { 28407 locale: currentLocale, 28408 sync: sync, 28409 loadParms: loadParams, 28410 onLoad: ilib.bind(this, function (data) { 28411 if (data && !data.invalid) { 28412 norm = data; 28413 } 28414 28415 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 28416 if (typeof(callback) === 'function') { 28417 callback(formatted); 28418 } 28419 }) 28420 }); 28421 } else { 28422 formatted = this._doNormalize(options, norm, homeLocale, currentLocale, currentPlan, destinationLocale, destinationPlan, sync, loadParams); 28423 if (typeof(callback) === 'function') { 28424 callback(formatted); 28425 } 28426 } 28427 }, 28428 28429 /** 28430 * This function normalizes the current phone number to a canonical format and returns a 28431 * string with that phone number. If parts are missing, this function attempts to fill in 28432 * those parts.<p> 28433 * 28434 * The options object contains a set of properties that can possibly help normalize 28435 * this number by providing "extra" information to the algorithm. The options 28436 * parameter may be null or an empty object if no hints can be determined before 28437 * this call is made. If any particular hint is not 28438 * available, it does not need to be present in the options object.<p> 28439 * 28440 * The following is a list of hints that the algorithm will look for in the options 28441 * object: 28442 * 28443 * <ul> 28444 * <li><i>mcc</i> the mobile carrier code of the current network upon which this 28445 * phone is operating. This is translated into an IDD country code. This is 28446 * useful if the number being normalized comes from CNAP (callerid) and the 28447 * MCC is known. 28448 * <li><i>defaultAreaCode</i> the area code of the phone number of the current 28449 * device, if available. Local numbers in a person's contact list are most 28450 * probably in this same area code. 28451 * <li><i>country</i> the 2 letter ISO 3166 code of the country if it is 28452 * known from some other means such as parsing the physical address of the 28453 * person associated with the phone number, or the from the domain name 28454 * of the person's email address 28455 * <li><i>networkType</i> specifies whether the phone is currently connected to a 28456 * CDMA network or a UMTS network. Valid values are the strings "cdma" and "umts". 28457 * If one of those two strings are not specified, or if this property is left off 28458 * completely, this method will assume UMTS. 28459 * </ul> 28460 * 28461 * The following are a list of options that control the behaviour of the normalization: 28462 * 28463 * <ul> 28464 * <li><i>assistedDialing</i> if this is set to true, the number will be normalized 28465 * so that it can dialled directly on the type of network this phone is 28466 * currently connected to. This allows customers to dial numbers or use numbers 28467 * in their contact list that are specific to their "home" region when they are 28468 * roaming and those numbers would not otherwise work with the current roaming 28469 * carrier as they are. The home region is 28470 * specified as the phoneRegion system preference that is settable in the 28471 * regional settings app. With assisted dialling, this method will add or 28472 * remove international direct dialling prefixes and country codes, as well as 28473 * national trunk access codes, as required by the current roaming carrier and the 28474 * home region in order to dial the number properly. If it is not possible to 28475 * construct a full international dialling sequence from the options and hints given, 28476 * this function will not modify the phone number, and will return "undefined". 28477 * If assisted dialling is false or not specified, then this method will attempt 28478 * to add all the information it can to the number so that it is as fully 28479 * specified as possible. This allows two numbers to be compared more easily when 28480 * those two numbers were otherwise only partially specified. 28481 * <li><i>sms</i> set this option to true for the following conditions: 28482 * <ul> 28483 * <li>assisted dialing is turned on 28484 * <li>the phone number represents the destination of an SMS message 28485 * <li>the phone is UMTS 28486 * <li>the phone is SIM-locked to its carrier 28487 * </ul> 28488 * This enables special international direct dialling codes to route the SMS message to 28489 * the correct carrier. If assisted dialling is not turned on, this option has no 28490 * affect. 28491 * <li><i>manualDialing</i> set this option to true if the user is entering this number on 28492 * the keypad directly, and false when the number comes from a stored location like a 28493 * contact entry or a call log entry. When true, this option causes the normalizer to 28494 * not perform any normalization on numbers that look like local numbers in the home 28495 * country. If false, all numbers go through normalization. This option only has an effect 28496 * when the assistedDialing option is true as well, otherwise it is ignored. 28497 * </ul> 28498 * 28499 * If both a set of options and a locale are given, and they offer conflicting 28500 * information, the options will take precedence. The idea is that the locale 28501 * tells you the region setting that the user has chosen (probably in 28502 * firstuse), whereas the the hints are more current information such as 28503 * where the phone is currently operating (the MCC).<p> 28504 * 28505 * This function performs the following types of normalizations with assisted 28506 * dialling turned on: 28507 * 28508 * <ol> 28509 * <li>If the current location of the phone matches the home country, this is a 28510 * domestic call. 28511 * <ul> 28512 * <li>Remove any iddPrefix and countryCode fields, as they are not needed 28513 * <li>Add in a trunkAccess field that may be necessary to call a domestic numbers 28514 * in the home country 28515 * </ul> 28516 * <li> If the current location of the phone does not match the home country, 28517 * attempt to form a whole international number. 28518 * <ul> 28519 * <li>Add in the area code if it is missing from the phone number and the area code 28520 * of the current phone is available in the hints 28521 * <li>Add the country dialling code for the home country if it is missing from the 28522 * phone number 28523 * <li>Add or replace the iddPrefix with the correct one for the current country. The 28524 * phone number will have been parsed with the settings for the home country, so 28525 * the iddPrefix may be incorrect for the 28526 * current country. The iddPrefix for the current country can be "+" if the phone 28527 * is connected to a UMTS network, and either a "+" or a country-dependent 28528 * sequences of digits for CDMA networks. 28529 * </ul> 28530 * </ol> 28531 * 28532 * This function performs the following types of normalization with assisted 28533 * dialling turned off: 28534 * 28535 * <ul> 28536 * <li>Normalize the international direct dialing prefix to be a plus or the 28537 * international direct dialling access code for the current country, depending 28538 * on the network type. 28539 * <li>If a number is a local number (ie. it is missing its area code), 28540 * use a default area code from the hints if available. CDMA phones always know their area 28541 * code, and GSM/UMTS phones know their area code in many instances, but not always 28542 * (ie. not on Vodaphone or Telcel phones). If the default area code is not available, 28543 * do not add it. 28544 * <li>In assisted dialling mode, if a number is missing its country code, 28545 * use the current MCC number if 28546 * it is available to figure out the current country code, and prepend that 28547 * to the number. If it is not available, leave it off. Also, use that 28548 * country's settings to parse the number instead of the current format 28549 * locale. 28550 * <li>For North American numbers with an area code but no trunk access 28551 * code, add in the trunk access code. 28552 * <li>For other countries, if the country code is added in step 3, remove the 28553 * trunk access code when required by that country's conventions for 28554 * international calls. If the country requires a trunk access code for 28555 * international calls and it doesn't exist, add one. 28556 * </ul> 28557 * 28558 * This method modifies the current object, and also returns a string 28559 * containing the normalized phone number that can be compared directly against 28560 * other normalized numbers. The canonical format for phone numbers that is 28561 * returned from thhomeLocaleis method is simply an uninterrupted and unformatted string 28562 * of dialable digits. 28563 * 28564 * @param {{ 28565 * mcc:string, 28566 * defaultAreaCode:string, 28567 * country:string, 28568 * networkType:string, 28569 * assistedDialing:boolean, 28570 * sms:boolean, 28571 * manualDialing:boolean 28572 * }} options an object containing options to help in normalizing. 28573 * @return {string|undefined} the normalized string, or undefined if the number 28574 * could not be normalized 28575 */ 28576 normalize: function(options) { 28577 var norm, 28578 sync = true, 28579 loadParams = {}; 28580 28581 28582 if (options) { 28583 if (typeof(options.sync) !== 'undefined') { 28584 sync = !!options.sync; 28585 } 28586 28587 if (options.loadParams) { 28588 loadParams = options.loadParams; 28589 } 28590 } 28591 28592 // Clone this number, so we don't mess with the original. 28593 // No need to do this asynchronously because it's a copy constructor which doesn't 28594 // load any extra files. 28595 norm = new PhoneNumber(this); 28596 28597 var normalized; 28598 28599 if (options && (typeof(options.mcc) !== 'undefined' || typeof(options.country) !== 'undefined')) { 28600 new PhoneLocale({ 28601 mcc: options.mcc, 28602 countryCode: options.countryCode, 28603 locale: this.locale, 28604 sync: sync, 28605 loadParams: loadParams, 28606 onLoad: ilib.bind(this, function(loc) { 28607 new NumberingPlan({ 28608 locale: loc, 28609 sync: sync, 28610 loadParms: loadParams, 28611 onLoad: ilib.bind(this, function (plan) { 28612 this._doReparse(options, norm, this.locale, loc, plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 28613 normalized = fmt; 28614 28615 if (options && typeof(options.onLoad) === 'function') { 28616 options.onLoad(fmt); 28617 } 28618 }); 28619 }) 28620 }); 28621 }) 28622 }); 28623 } else { 28624 this._doReparse(options, norm, this.locale, this.locale, this.plan, this.destinationLocale, this.destinationPlan, sync, loadParams, function (fmt) { 28625 normalized = fmt; 28626 28627 if (options && typeof(options.onLoad) === 'function') { 28628 options.onLoad(fmt); 28629 } 28630 }); 28631 } 28632 28633 // return the value for the synchronous case 28634 return normalized; 28635 } 28636 }; 28637 28638 28639 /*< PhoneFmt.js */ 28640 /* 28641 * phonefmt.js - Represent a phone number formatter. 28642 * 28643 * Copyright © 2014-2015, JEDLSoft 28644 * 28645 * Licensed under the Apache License, Version 2.0 (the "License"); 28646 * you may not use this file except in compliance with the License. 28647 * You may obtain a copy of the License at 28648 * 28649 * http://www.apache.org/licenses/LICENSE-2.0 28650 * 28651 * Unless required by applicable law or agreed to in writing, software 28652 * distributed under the License is distributed on an "AS IS" BASIS, 28653 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 28654 * 28655 * See the License for the specific language governing permissions and 28656 * limitations under the License. 28657 */ 28658 28659 /* 28660 !depends 28661 ilib.js 28662 Locale.js 28663 NumberingPlan.js 28664 PhoneNumber.js 28665 PhoneLocale.js 28666 Utils.js 28667 JSUtils.js 28668 */ 28669 28670 // !data phonefmt 28671 28672 28673 /** 28674 * @class 28675 * Create a new phone number formatter object that formats numbers according to the parameters.<p> 28676 * 28677 * The options object can contain zero or more of the following parameters: 28678 * 28679 * <ul> 28680 * <li><i>locale</i> locale to use to format this number, or undefined to use the default locale 28681 * <li><i>style</i> the name of style to use to format numbers, or undefined to use the default style 28682 * <li><i>mcc</i> the MCC of the country to use if the number is a local number and the country code is not known 28683 * 28684 * <li><i>onLoad</i> - a callback function to call when the locale data is fully loaded and the address has been 28685 * parsed. When the onLoad option is given, the address formatter object 28686 * will attempt to load any missing locale data using the ilib loader callback. 28687 * When the constructor is done (even if the data is already preassembled), the 28688 * onLoad function is called with the current instance as a parameter, so this 28689 * callback can be used with preassembled or dynamic loading or a mix of the two. 28690 * 28691 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 28692 * asynchronously. If this option is given as "false", then the "onLoad" 28693 * callback must be given, as the instance returned from this constructor will 28694 * not be usable for a while. 28695 * 28696 * <li><i>loadParams</i> - an object containing parameters to pass to the 28697 * loader callback function when locale data is missing. The parameters are not 28698 * interpretted or modified in any way. They are simply passed along. The object 28699 * may contain any property/value pairs as long as the calling code is in 28700 * agreement with the loader callback function as to what those parameters mean. 28701 * </ul> 28702 * 28703 * Some regions have more than one style of formatting, and the style parameter 28704 * selects which style the user prefers. An array of style names that this locale 28705 * supports can be found by calling {@link PhoneFmt.getAvailableStyles}. 28706 * Example phone numbers can be retrieved for each style by calling 28707 * {@link PhoneFmt.getStyleExample}. 28708 * <p> 28709 * 28710 * If the MCC is given, numbers will be formatted in the manner of the country 28711 * specified by the MCC. If it is not given, but the locale is, the manner of 28712 * the country in the locale will be used. If neither the locale or MCC are not given, 28713 * then the country of the current ilib locale is used. 28714 * 28715 * @constructor 28716 * @param {Object} options properties that control how this formatter behaves 28717 */ 28718 var PhoneFmt = function(options) { 28719 this.sync = true; 28720 this.styleName = 'default', 28721 this.loadParams = {}; 28722 28723 var locale = new Locale(); 28724 28725 if (options) { 28726 if (options.locale) { 28727 locale = options.locale; 28728 } 28729 28730 if (typeof(options.sync) !== 'undefined') { 28731 this.sync = !!options.sync; 28732 } 28733 28734 if (options.loadParams) { 28735 this.loadParams = options.loadParams; 28736 } 28737 28738 if (options.style) { 28739 this.style = options.style; 28740 } 28741 } 28742 28743 new PhoneLocale({ 28744 locale: locale, 28745 mcc: options && options.mcc, 28746 countryCode: options && options.countryCode, 28747 onLoad: ilib.bind(this, function (data) { 28748 /** @type {PhoneLocale} */ 28749 this.locale = data; 28750 28751 new NumberingPlan({ 28752 locale: this.locale, 28753 sync: this.sync, 28754 loadParms: this.loadParams, 28755 onLoad: ilib.bind(this, function (plan) { 28756 /** @type {NumberingPlan} */ 28757 this.plan = plan; 28758 28759 Utils.loadData({ 28760 name: "phonefmt.json", 28761 object: "PhoneFmt", 28762 locale: this.locale, 28763 sync: this.sync, 28764 loadParams: JSUtils.merge(this.loadParams, { 28765 returnOne: true 28766 }), 28767 callback: ilib.bind(this, function (fmtdata) { 28768 this.fmtdata = fmtdata; 28769 28770 if (options && typeof(options.onLoad) === 'function') { 28771 options.onLoad(this); 28772 } 28773 }) 28774 }); 28775 }) 28776 }); 28777 }) 28778 }); 28779 }; 28780 28781 PhoneFmt.prototype = { 28782 /** 28783 * 28784 * @protected 28785 * @param {string} part 28786 * @param {Object} formats 28787 * @param {boolean} mustUseAll 28788 */ 28789 _substituteDigits: function(part, formats, mustUseAll) { 28790 var formatString, 28791 formatted = "", 28792 partIndex = 0, 28793 templates, 28794 i; 28795 28796 // console.info("Globalization.Phone._substituteDigits: typeof(formats) is " + typeof(formats)); 28797 if (!part) { 28798 return formatted; 28799 } 28800 28801 if (typeof(formats) === "object") { 28802 templates = (typeof(formats.template) !== 'undefined') ? formats.template : formats; 28803 if (part.length > templates.length) { 28804 // too big, so just use last resort rule. 28805 throw "part " + part + " is too big. We do not have a format template to format it."; 28806 } 28807 // use the format in this array that corresponds to the digit length of this 28808 // part of the phone number 28809 formatString = templates[part.length-1]; 28810 // console.info("Globalization.Phone._substituteDigits: formats is an Array: " + JSON.stringify(formats)); 28811 } else { 28812 formatString = formats; 28813 } 28814 28815 for (i = 0; i < formatString.length; i++) { 28816 if (formatString.charAt(i) === "X") { 28817 formatted += part.charAt(partIndex); 28818 partIndex++; 28819 } else { 28820 formatted += formatString.charAt(i); 28821 } 28822 } 28823 28824 if (mustUseAll && partIndex < part.length-1) { 28825 // didn't use the whole thing in this format? Hmm... go to last resort rule 28826 throw "too many digits in " + part + " for format " + formatString; 28827 } 28828 28829 return formatted; 28830 }, 28831 28832 /** 28833 * Returns the style with the given name, or the default style if there 28834 * is no style with that name. 28835 * @protected 28836 * @return {{example:string,whole:Object.<string,string>,partial:Object.<string,string>}|Object.<string,string>} 28837 */ 28838 _getStyle: function (name, fmtdata) { 28839 return fmtdata[name] || fmtdata["default"]; 28840 }, 28841 28842 /** 28843 * Do the actual work of formatting the phone number starting at the given 28844 * field in the regular field order. 28845 * 28846 * @param {!PhoneNumber} number 28847 * @param {{ 28848 * partial:boolean, 28849 * style:string, 28850 * mcc:string, 28851 * locale:(string|Locale), 28852 * sync:boolean, 28853 * loadParams:Object, 28854 * onLoad:function(string) 28855 * }} options Parameters which control how to format the number 28856 * @param {number} startField 28857 */ 28858 _doFormat: function(number, options, startField, locale, fmtdata, callback) { 28859 var sync = true, 28860 loadParams = {}, 28861 temp, 28862 templates, 28863 fieldName, 28864 countryCode, 28865 isWhole, 28866 style, 28867 formatted = "", 28868 styleTemplates, 28869 lastFieldName; 28870 28871 if (options) { 28872 if (typeof(options.sync) !== 'undefined') { 28873 sync = !!options.sync; 28874 } 28875 28876 if (options.loadParams) { 28877 loadParams = options.loadParams; 28878 } 28879 } 28880 28881 style = this.style; // default style for this formatter 28882 28883 // figure out what style to use for this type of number 28884 if (number.countryCode) { 28885 // dialing from outside the country 28886 // check to see if it to a mobile number because they are often formatted differently 28887 style = (number.mobilePrefix) ? "internationalmobile" : "international"; 28888 } else if (number.mobilePrefix !== undefined) { 28889 style = "mobile"; 28890 } else if (number.serviceCode !== undefined && typeof(fmtdata["service"]) !== 'undefined') { 28891 // if there is a special format for service numbers, then use it 28892 style = "service"; 28893 } 28894 28895 isWhole = (!options || !options.partial); 28896 styleTemplates = this._getStyle(style, fmtdata); 28897 28898 // console.log("Style ends up being " + style + " and using subtype " + (isWhole ? "whole" : "partial")); 28899 styleTemplates = (isWhole ? styleTemplates.whole : styleTemplates.partial) || styleTemplates; 28900 28901 for (var i = startField; i < PhoneNumber._fieldOrder.length; i++) { 28902 fieldName = PhoneNumber._fieldOrder[i]; 28903 // console.info("format: formatting field " + fieldName + " value: " + number[fieldName]); 28904 if (number[fieldName] !== undefined) { 28905 if (styleTemplates[fieldName] !== undefined) { 28906 templates = styleTemplates[fieldName]; 28907 if (fieldName === "trunkAccess") { 28908 if (number.areaCode === undefined && number.serviceCode === undefined && number.mobilePrefix === undefined) { 28909 templates = "X"; 28910 } 28911 } 28912 if (lastFieldName && typeof(styleTemplates[lastFieldName].suffix) !== 'undefined') { 28913 if (fieldName !== "extension" && number[fieldName].search(/[xwtp,;]/i) <= -1) { 28914 formatted += styleTemplates[lastFieldName].suffix; 28915 } 28916 } 28917 lastFieldName = fieldName; 28918 28919 // console.info("format: formatting field " + fieldName + " with templates " + JSON.stringify(templates)); 28920 temp = this._substituteDigits(number[fieldName], templates, (fieldName === "subscriberNumber")); 28921 // console.info("format: formatted is: " + temp); 28922 formatted += temp; 28923 28924 if (fieldName === "countryCode") { 28925 // switch to the new country to format the rest of the number 28926 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 28927 28928 new PhoneLocale({ 28929 locale: this.locale, 28930 sync: sync, 28931 loadParms: loadParams, 28932 countryCode: countryCode, 28933 onLoad: ilib.bind(this, function (locale) { 28934 Utils.loadData({ 28935 name: "phonefmt.json", 28936 object: "PhoneFmt", 28937 locale: locale, 28938 sync: sync, 28939 loadParams: JSUtils.merge(loadParams, { 28940 returnOne: true 28941 }), 28942 callback: ilib.bind(this, function (fmtdata) { 28943 // console.info("format: switching to region " + locale.region + " and style " + style + " to format the rest of the number "); 28944 28945 var subfmt = ""; 28946 28947 this._doFormat(number, options, i+1, locale, fmtdata, function (subformat) { 28948 subfmt = subformat; 28949 if (typeof(callback) === 'function') { 28950 callback(formatted + subformat); 28951 } 28952 }); 28953 28954 formatted += subfmt; 28955 }) 28956 }); 28957 }) 28958 }); 28959 return formatted; 28960 } 28961 } else { 28962 //console.warn("PhoneFmt.format: cannot find format template for field " + fieldName + ", region " + locale.region + ", style " + style); 28963 // use default of "minimal formatting" so we don't miss parts because of bugs in the format templates 28964 formatted += number[fieldName]; 28965 } 28966 } 28967 } 28968 28969 if (typeof(callback) === 'function') { 28970 callback(formatted); 28971 } 28972 28973 return formatted; 28974 }, 28975 28976 /** 28977 * Format the parts of a phone number appropriately according to the settings in 28978 * this formatter instance. 28979 * 28980 * The options can contain zero or more of these properties: 28981 * 28982 * <ul> 28983 * <li><i>partial</i> boolean which tells whether or not this phone number 28984 * represents a partial number or not. The default is false, which means the number 28985 * represents a whole number. 28986 * <li><i>style</i> style to use to format the number, if different from the 28987 * default style or the style specified in the constructor 28988 * <li><i>locale</i> The locale with which to parse the number. This gives a clue as to which 28989 * numbering plan to use. 28990 * <li><i>mcc</i> The mobile carrier code (MCC) associated with the carrier that the phone is 28991 * currently connected to, if known. This also can give a clue as to which numbering plan to 28992 * use 28993 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 28994 * loaded. When the onLoad option is given, the DateFmt object will attempt to 28995 * load any missing locale data using the ilib loader callback. 28996 * When the constructor is done (even if the data is already preassembled), the 28997 * onLoad function is called with the current instance as a parameter, so this 28998 * callback can be used with preassembled or dynamic loading or a mix of the two. 28999 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 29000 * asynchronously. If this option is given as "false", then the "onLoad" 29001 * callback must be given, as the instance returned from this constructor will 29002 * not be usable for a while. 29003 * <li><i>loadParams</i> - an object containing parameters to pass to the 29004 * loader callback function when locale data is missing. The parameters are not 29005 * interpretted or modified in any way. They are simply passed along. The object 29006 * may contain any property/value pairs as long as the calling code is in 29007 * agreement with the loader callback function as to what those parameters mean. 29008 * </ul> 29009 * 29010 * The partial parameter specifies whether or not the phone number contains 29011 * a partial phone number or if it is a whole phone number. A partial 29012 * number is usually a number as the user is entering it with a dial pad. The 29013 * reason is that certain types of phone numbers should be formatted differently 29014 * depending on whether or not it represents a whole number. Specifically, SMS 29015 * short codes are formatted differently.<p> 29016 * 29017 * Example: a subscriber number of "48773" in the US would get formatted as: 29018 * 29019 * <ul> 29020 * <li>partial: 487-73 (perhaps the user is in the process of typing a whole phone 29021 * number such as 487-7379) 29022 * <li>whole: 48773 (this is the entire SMS short code) 29023 * </ul> 29024 * 29025 * Any place in the UI where the user types in phone numbers, such as the keypad in 29026 * the phone app, should pass in partial: true to this formatting routine. All other 29027 * places, such as the call log in the phone app, should pass in partial: false, or 29028 * leave the partial flag out of the parameters entirely. 29029 * 29030 * @param {!PhoneNumber} number object containing the phone number to format 29031 * @param {{ 29032 * partial:boolean, 29033 * style:string, 29034 * mcc:string, 29035 * locale:(string|Locale), 29036 * sync:boolean, 29037 * loadParams:Object, 29038 * onLoad:function(string) 29039 * }} options Parameters which control how to format the number 29040 * @return {string} Returns the formatted phone number as a string. 29041 */ 29042 format: function (number, options) { 29043 var formatted = "", 29044 callback; 29045 29046 callback = options && options.onLoad; 29047 29048 try { 29049 this._doFormat(number, options, 0, this.locale, this.fmtdata, function (fmt) { 29050 formatted = fmt; 29051 29052 if (typeof(callback) === 'function') { 29053 callback(fmt); 29054 } 29055 }); 29056 } catch (e) { 29057 if (typeof(e) === 'string') { 29058 // console.warn("caught exception: " + e + ". Using last resort rule."); 29059 // if there was some exception, use this last resort rule 29060 formatted = ""; 29061 for (var field in PhoneNumber._fieldOrder) { 29062 if (typeof field === 'string' && typeof PhoneNumber._fieldOrder[field] === 'string' && number[PhoneNumber._fieldOrder[field]] !== undefined) { 29063 // just concatenate without any formatting 29064 formatted += number[PhoneNumber._fieldOrder[field]]; 29065 if (PhoneNumber._fieldOrder[field] === 'countryCode') { 29066 formatted += ' '; // fix for NOV-107894 29067 } 29068 } 29069 } 29070 } else { 29071 throw e; 29072 } 29073 29074 if (typeof(callback) === 'function') { 29075 callback(formatted); 29076 } 29077 } 29078 return formatted; 29079 }, 29080 29081 /** 29082 * Return an array of names of all available styles that can be used with the current 29083 * formatter. 29084 * @return {Array.<string>} an array of names of styles that are supported by this formatter 29085 */ 29086 getAvailableStyles: function () { 29087 var ret = [], 29088 style; 29089 29090 if (this.fmtdata) { 29091 for (style in this.fmtdata) { 29092 if (this.fmtdata[style].example) { 29093 ret.push(style); 29094 } 29095 } 29096 } 29097 return ret; 29098 }, 29099 29100 /** 29101 * Return an example phone number formatted with the given style. 29102 * 29103 * @param {string|undefined} style style to get an example of, or undefined to use 29104 * the current default style for this formatter 29105 * @return {string|undefined} an example phone number formatted according to the 29106 * given style, or undefined if the style is not recognized or does not have an 29107 * example 29108 */ 29109 getStyleExample: function (style) { 29110 return this.fmtdata[style].example || undefined; 29111 } 29112 }; 29113 29114 29115 /*< PhoneGeoLocator.js */ 29116 /* 29117 * PhoneGeoLocator.js - Represent a phone number geolocator object. 29118 * 29119 * Copyright © 2014-2015, 2018 JEDLSoft 29120 * 29121 * Licensed under the Apache License, Version 2.0 (the "License"); 29122 * you may not use this file except in compliance with the License. 29123 * You may obtain a copy of the License at 29124 * 29125 * http://www.apache.org/licenses/LICENSE-2.0 29126 * 29127 * Unless required by applicable law or agreed to in writing, software 29128 * distributed under the License is distributed on an "AS IS" BASIS, 29129 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29130 * 29131 * See the License for the specific language governing permissions and 29132 * limitations under the License. 29133 */ 29134 29135 /* 29136 !depends 29137 ilib.js 29138 NumberingPlan.js 29139 PhoneLocale.js 29140 PhoneNumber.js 29141 Utils.js 29142 JSUtils.js 29143 ResBundle.js 29144 */ 29145 29146 // !data iddarea area extarea extstates phoneres 29147 29148 29149 29150 /** 29151 * @class 29152 * Create an instance that can geographically locate a phone number.<p> 29153 * 29154 * The location of the number is calculated according to the following rules: 29155 * 29156 * <ol> 29157 * <li>If the areaCode property is undefined or empty, or if the number specifies a 29158 * country code for which we do not have information, then the area property may be 29159 * missing from the returned object. In this case, only the country object will be returned. 29160 * 29161 * <li>If there is no area code, but there is a mobile prefix, service code, or emergency 29162 * code, then a fixed string indicating the type of number will be returned. 29163 * 29164 * <li>The country object is filled out according to the countryCode property of the phone 29165 * number. 29166 * 29167 * <li>If the phone number does not have an explicit country code, the MCC will be used if 29168 * it is available. The country code can be gleaned directly from the MCC. If the MCC 29169 * of the carrier to which the phone is currently connected is available, it should be 29170 * passed in so that local phone numbers will look correct. 29171 * 29172 * <li>If the country's dialling plan mandates a fixed length for phone numbers, and a 29173 * particular number exceeds that length, then the area code will not be given on the 29174 * assumption that the number has problems in the first place and we cannot guess 29175 * correctly. 29176 * </ol> 29177 * 29178 * The returned area property varies in specificity according 29179 * to the locale. In North America, the area is no finer than large parts of states 29180 * or provinces. In Germany and the UK, the area can be as fine as small towns.<p> 29181 * 29182 * If the number passed in is invalid, no geolocation will be performed. If the location 29183 * information about the country where the phone number is located is not available, 29184 * then the area information will be missing and only the country will be available.<p> 29185 * 29186 * The options parameter can contain any one of the following properties: 29187 * 29188 * <ul> 29189 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 29190 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 29191 * but the phone number being geolocated is in Germany, then this class would return the the names 29192 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 29193 * phone number in Munich and return the country "Germany" and the area code "Munich" 29194 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 29195 * If translations are not available, the region and area names are given in English, which should 29196 * always be available. 29197 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 29198 * 29199 * <li><i>onLoad</i> - a callback function to call when the data for the 29200 * locale is fully loaded. When the onLoad option is given, this object 29201 * will attempt to load any missing locale data using the ilib loader callback. 29202 * When the constructor is done (even if the data is already preassembled), the 29203 * onLoad function is called with the current instance as a parameter, so this 29204 * callback can be used with preassembled or dynamic loading or a mix of the two. 29205 * 29206 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 29207 * asynchronously. If this option is given as "false", then the "onLoad" 29208 * callback must be given, as the instance returned from this constructor will 29209 * not be usable for a while. 29210 * 29211 * <li><i>loadParams</i> - an object containing parameters to pass to the 29212 * loader callback function when locale data is missing. The parameters are not 29213 * interpretted or modified in any way. They are simply passed along. The object 29214 * may contain any property/value pairs as long as the calling code is in 29215 * agreement with the loader callback function as to what those parameters mean. 29216 * </ul> 29217 * 29218 * @constructor 29219 * @param {Object} options parameters controlling the geolocation of the phone number. 29220 */ 29221 var PhoneGeoLocator = function(options) { 29222 var sync = true, 29223 loadParams = {}, 29224 locale = ilib.getLocale(); 29225 29226 if (options) { 29227 if (options.locale) { 29228 locale = options.locale; 29229 } 29230 29231 if (typeof(options.sync) === 'boolean') { 29232 sync = options.sync; 29233 } 29234 29235 if (options.loadParams) { 29236 loadParams = options.loadParams; 29237 } 29238 } 29239 29240 new PhoneLocale({ 29241 locale: locale, 29242 mcc: options && options.mcc, 29243 countryCode: options && options.countryCode, 29244 sync: sync, 29245 loadParams: loadParams, 29246 onLoad: ilib.bind(this, function (loc) { 29247 this.locale = loc; 29248 new NumberingPlan({ 29249 locale: this.locale, 29250 sync: sync, 29251 loadParams: loadParams, 29252 onLoad: ilib.bind(this, function (plan) { 29253 this.plan = plan; 29254 29255 new ResBundle({ 29256 locale: this.locale, 29257 name: "phoneres", 29258 sync: sync, 29259 loadParams: loadParams, 29260 onLoad: ilib.bind(this, function (rb) { 29261 this.rb = rb; 29262 29263 Utils.loadData({ 29264 name: "iddarea.json", 29265 object: "PhoneGeoLocator", 29266 nonlocale: true, 29267 sync: sync, 29268 loadParams: loadParams, 29269 callback: ilib.bind(this, function (data) { 29270 this.regiondata = data; 29271 Utils.loadData({ 29272 name: "area.json", 29273 object: "PhoneGeoLocator", 29274 locale: this.locale, 29275 sync: sync, 29276 loadParams: JSUtils.merge(loadParams, { 29277 returnOne: true 29278 }), 29279 callback: ilib.bind(this, function (areadata) { 29280 this.areadata = areadata; 29281 29282 if (options && typeof(options.onLoad) === 'function') { 29283 options.onLoad(this); 29284 } 29285 }) 29286 }); 29287 }) 29288 }); 29289 }) 29290 }); 29291 }) 29292 }); 29293 }) 29294 }); 29295 }; 29296 29297 PhoneGeoLocator.prototype = { 29298 /** 29299 * @private 29300 * 29301 * Used for locales where the area code is very general, and you need to add in 29302 * the initial digits of the subscriber number in order to get the area 29303 * 29304 * @param {string} number 29305 * @param {Object} stateTable 29306 */ 29307 _parseAreaAndSubscriber: function (number, stateTable) { 29308 var ch, 29309 i, 29310 handlerMethod, 29311 newState, 29312 consumed, 29313 lastLeaf, 29314 currentState, 29315 dot = 14; // special transition which matches all characters. See AreaCodeTableMaker.java 29316 29317 if (!number || !stateTable) { 29318 // can't parse anything 29319 return undefined; 29320 } 29321 29322 //console.log("GeoLocator._parseAreaAndSubscriber: parsing number " + number); 29323 29324 currentState = stateTable; 29325 i = 0; 29326 while (i < number.length) { 29327 ch = PhoneNumber._getCharacterCode(number.charAt(i)); 29328 if (ch >= 0) { 29329 // newState = stateData.states[state][ch]; 29330 newState = currentState.s && currentState.s[ch]; 29331 29332 if (!newState && currentState.s && currentState.s[dot]) { 29333 newState = currentState.s[dot]; 29334 } 29335 29336 if (typeof(newState) === 'object') { 29337 if (typeof(newState.l) !== 'undefined') { 29338 // save for latter if needed 29339 lastLeaf = newState; 29340 consumed = i; 29341 } 29342 // console.info("recognized digit " + ch + " continuing..."); 29343 // recognized digit, so continue parsing 29344 currentState = newState; 29345 i++; 29346 } else { 29347 if (typeof(newState) === 'undefined' || newState === 0) { 29348 // this is possibly a look-ahead and it didn't work... 29349 // so fall back to the last leaf and use that as the 29350 // final state 29351 newState = lastLeaf; 29352 i = consumed; 29353 } 29354 29355 if ((typeof(newState) === 'number' && newState) || 29356 (typeof(newState) === 'object' && typeof(newState.l) !== 'undefined')) { 29357 // final state 29358 var stateNumber = typeof(newState) === 'number' ? newState : newState.l; 29359 handlerMethod = PhoneNumber._states[stateNumber]; 29360 29361 //console.info("reached final state " + newState + " handler method is " + handlerMethod + " and i is " + i); 29362 29363 return (handlerMethod === "area") ? number.substring(0, i+1) : undefined; 29364 } else { 29365 // failed parse. Either no last leaf to fall back to, or there was an explicit 29366 // zero in the table 29367 break; 29368 } 29369 } 29370 } else if (ch === -1) { 29371 // non-transition character, continue parsing in the same state 29372 i++; 29373 } else { 29374 // should not happen 29375 // console.info("skipping character " + ch); 29376 // not a digit, plus, pound, or star, so this is probably a formatting char. Skip it. 29377 i++; 29378 } 29379 } 29380 return undefined; 29381 }, 29382 /** 29383 * @private 29384 * @param prefix 29385 * @param table 29386 * @returns 29387 */ 29388 _matchPrefix: function(prefix, table) { 29389 var i, matchedDot, matchesWithDots = []; 29390 29391 if (table[prefix]) { 29392 return table[prefix]; 29393 } 29394 for (var entry in table) { 29395 if (entry && typeof(entry) === 'string') { 29396 i = 0; 29397 matchedDot = false; 29398 while (i < entry.length && (entry.charAt(i) === prefix.charAt(i) || entry.charAt(i) === '.')) { 29399 if (entry.charAt(i) === '.') { 29400 matchedDot = true; 29401 } 29402 i++; 29403 } 29404 if (i >= entry.length) { 29405 if (matchedDot) { 29406 matchesWithDots.push(entry); 29407 } else { 29408 return table[entry]; 29409 } 29410 } 29411 } 29412 } 29413 29414 // match entries with dots last, so sort the matches so that the entry with the 29415 // most dots sorts last. The entry that ends up at the beginning of the list is 29416 // the best match because it has the fewest dots 29417 if (matchesWithDots.length > 0) { 29418 matchesWithDots.sort(function (left, right) { 29419 return (right < left) ? -1 : ((left < right) ? 1 : 0); 29420 }); 29421 return table[matchesWithDots[0]]; 29422 } 29423 29424 return undefined; 29425 }, 29426 /** 29427 * @private 29428 * @param number 29429 * @param data 29430 * @param locale 29431 * @param plan 29432 * @param options 29433 * @returns {Object} 29434 */ 29435 _getAreaInfo: function(number, data, locale, plan, options) { 29436 var sync = true, 29437 ret = {}, 29438 countryCode, 29439 areaInfo, 29440 temp, 29441 areaCode, 29442 geoTable, 29443 tempNumber, 29444 prefix; 29445 29446 if (options && typeof(options.sync) === 'boolean') { 29447 sync = options.sync; 29448 } 29449 29450 prefix = number.areaCode || number.serviceCode; 29451 geoTable = data; 29452 29453 if (prefix !== undefined) { 29454 if (plan.getExtendedAreaCode()) { 29455 // for countries where the area code is very general and large, and you need a few initial 29456 // digits of the subscriber number in order find the actual area 29457 tempNumber = prefix + number.subscriberNumber; 29458 tempNumber = tempNumber.replace(/[wWpPtT\+#\*]/g, ''); // fix for NOV-108200 29459 29460 Utils.loadData({ 29461 name: "extarea.json", 29462 object: "PhoneGeoLocator", 29463 locale: locale, 29464 sync: sync, 29465 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 29466 callback: ilib.bind(this, function (data) { 29467 this.extarea = data; 29468 Utils.loadData({ 29469 name: "extstates.json", 29470 object: "PhoneGeoLocator", 29471 locale: locale, 29472 sync: sync, 29473 loadParams: JSUtils.merge((options && options.loadParams) || {}, {returnOne: true}), 29474 callback: ilib.bind(this, function (data) { 29475 this.extstates = data; 29476 geoTable = this.extarea; 29477 if (this.extarea && this.extstates) { 29478 prefix = this._parseAreaAndSubscriber(tempNumber, this.extstates); 29479 } 29480 29481 if (!prefix) { 29482 // not a recognized prefix, so now try the general table 29483 geoTable = this.areadata; 29484 prefix = number.areaCode || number.serviceCode; 29485 } 29486 29487 if ((!plan.fieldLengths || 29488 plan.getFieldLength('maxLocalLength') === undefined || 29489 !number.subscriberNumber || 29490 number.subscriberNumber.length <= plan.fieldLengths('maxLocalLength'))) { 29491 areaInfo = this._matchPrefix(prefix, geoTable); 29492 if (areaInfo && areaInfo.sn && areaInfo.ln) { 29493 //console.log("Found areaInfo " + JSON.stringify(areaInfo)); 29494 ret.area = { 29495 sn: this.rb.getString(areaInfo.sn).toString(), 29496 ln: this.rb.getString(areaInfo.ln).toString() 29497 }; 29498 } 29499 } 29500 }) 29501 }); 29502 }) 29503 }); 29504 29505 } else if (!plan || 29506 plan.getFieldLength('maxLocalLength') === undefined || 29507 !number.subscriberNumber || 29508 number.subscriberNumber.length <= plan.getFieldLength('maxLocalLength')) { 29509 if (geoTable) { 29510 areaCode = prefix.replace(/[wWpPtT\+#\*]/g, ''); 29511 areaInfo = this._matchPrefix(areaCode, geoTable); 29512 29513 if (areaInfo && areaInfo.sn && areaInfo.ln) { 29514 ret.area = { 29515 sn: this.rb.getString(areaInfo.sn).toString(), 29516 ln: this.rb.getString(areaInfo.ln).toString() 29517 }; 29518 } else if (number.serviceCode) { 29519 ret.area = { 29520 sn: this.rb.getString("Service Number").toString(), 29521 ln: this.rb.getString("Service Number").toString() 29522 }; 29523 } 29524 } else { 29525 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 29526 if (countryCode !== "0" && this.regiondata) { 29527 temp = this.regiondata[countryCode]; 29528 if (temp && temp.sn) { 29529 ret.country = { 29530 sn: this.rb.getString(temp.sn).toString(), 29531 ln: this.rb.getString(temp.ln).toString(), 29532 code: this.locale.getRegion() 29533 }; 29534 } 29535 } 29536 } 29537 } else { 29538 countryCode = number.locale._mapRegiontoCC(this.locale.getRegion()); 29539 if (countryCode !== "0" && this.regiondata) { 29540 temp = this.regiondata[countryCode]; 29541 if (temp && temp.sn) { 29542 ret.country = { 29543 sn: this.rb.getString(temp.sn).toString(), 29544 ln: this.rb.getString(temp.ln).toString(), 29545 code: this.locale.getRegion() 29546 }; 29547 } 29548 } 29549 } 29550 29551 } else if (number.mobilePrefix) { 29552 ret.area = { 29553 sn: this.rb.getString("Mobile Number").toString(), 29554 ln: this.rb.getString("Mobile Number").toString() 29555 }; 29556 } else if (number.emergency) { 29557 ret.area = { 29558 sn: this.rb.getString("Emergency Services Number").toString(), 29559 ln: this.rb.getString("Emergency Services Number").toString() 29560 }; 29561 } 29562 29563 return ret; 29564 }, 29565 /** 29566 * Returns a the location of the given phone number, if known. 29567 * The returned object has 2 properties, each of which has an sn (short name) 29568 * and an ln (long name) string. Additionally, the country code, if given, 29569 * includes the 2 letter ISO code for the recognized country. 29570 * { 29571 * "country": { 29572 * "sn": "North America", 29573 * "ln": "North America and the Caribbean Islands", 29574 * "code": "us" 29575 * }, 29576 * "area": { 29577 * "sn": "California", 29578 * "ln": "Central California: San Jose, Los Gatos, Milpitas, Sunnyvale, Cupertino, Gilroy" 29579 * } 29580 * } 29581 * 29582 * The location name is subject to the following rules: 29583 * 29584 * If the areaCode property is undefined or empty, or if the number specifies a 29585 * country code for which we do not have information, then the area property may be 29586 * missing from the returned object. In this case, only the country object will be returned. 29587 * 29588 * If there is no area code, but there is a mobile prefix, service code, or emergency 29589 * code, then a fixed string indicating the type of number will be returned. 29590 * 29591 * The country object is filled out according to the countryCode property of the phone 29592 * number. 29593 * 29594 * If the phone number does not have an explicit country code, the MCC will be used if 29595 * it is available. The country code can be gleaned directly from the MCC. If the MCC 29596 * of the carrier to which the phone is currently connected is available, it should be 29597 * passed in so that local phone numbers will look correct. 29598 * 29599 * If the country's dialling plan mandates a fixed length for phone numbers, and a 29600 * particular number exceeds that length, then the area code will not be given on the 29601 * assumption that the number has problems in the first place and we cannot guess 29602 * correctly. 29603 * 29604 * The returned area property varies in specificity according 29605 * to the locale. In North America, the area is no finer than large parts of states 29606 * or provinces. In Germany and the UK, the area can be as fine as small towns. 29607 * 29608 * The strings returned from this function are already localized to the 29609 * given locale, and thus are ready for display to the user. 29610 * 29611 * If the number passed in is invalid, an empty object is returned. If the location 29612 * information about the country where the phone number is located is not available, 29613 * then the area information will be missing and only the country will be returned. 29614 * 29615 * The options parameter can contain any one of the following properties: 29616 * 29617 * <ul> 29618 * <li><i>locale</i> The locale parameter is used to load translations of the names of regions and 29619 * areas if available. For example, if the locale property is given as "en-US" (English for USA), 29620 * but the phone number being geolocated is in Germany, then this class would return the the names 29621 * of the country (Germany) and region inside of Germany in English instead of German. That is, a 29622 * phone number in Munich and return the country "Germany" and the area code "Munich" 29623 * instead of "Deutschland" and "München". The default display locale is the current ilib locale. 29624 * If translations are not available, the region and area names are given in English, which should 29625 * always be available. 29626 * <li><i>mcc</i> The mcc of the current mobile carrier, if known. 29627 * 29628 * <li><i>onLoad</i> - a callback function to call when the data for the 29629 * locale is fully loaded. When the onLoad option is given, this object 29630 * will attempt to load any missing locale data using the ilib loader callback. 29631 * When the constructor is done (even if the data is already preassembled), the 29632 * onLoad function is called with the current instance as a parameter, so this 29633 * callback can be used with preassembled or dynamic loading or a mix of the two. 29634 * 29635 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 29636 * asynchronously. If this option is given as "false", then the "onLoad" 29637 * callback must be given, as the instance returned from this constructor will 29638 * not be usable for a while. 29639 * 29640 * <li><i>loadParams</i> - an object containing parameters to pass to the 29641 * loader callback function when locale data is missing. The parameters are not 29642 * interpretted or modified in any way. They are simply passed along. The object 29643 * may contain any property/value pairs as long as the calling code is in 29644 * agreement with the loader callback function as to what those parameters mean. 29645 * </ul> 29646 * 29647 * @param {PhoneNumber} number phone number to locate 29648 * @param {Object} options options governing the way this ares is loaded 29649 * @return {Object} an object 29650 * that describes the country and the area in that country corresponding to this 29651 * phone number. Each of the country and area contain a short name (sn) and long 29652 * name (ln) that describes the location. 29653 */ 29654 locate: function(number, options) { 29655 var loadParams = {}, 29656 ret = {}, 29657 region, 29658 countryCode, 29659 temp, 29660 plan, 29661 areaResult, 29662 phoneLoc = this.locale, 29663 sync = true; 29664 29665 if (number === undefined || typeof(number) !== 'object' || !(number instanceof PhoneNumber)) { 29666 return ret; 29667 } 29668 29669 if (options) { 29670 if (typeof(options.sync) !== 'undefined') { 29671 sync = !!options.sync; 29672 } 29673 29674 if (options.loadParams) { 29675 loadParams = options.loadParams; 29676 } 29677 } 29678 29679 // console.log("GeoLocator.locate: looking for geo for number " + JSON.stringify(number)); 29680 region = this.locale.getRegion(); 29681 if (number.countryCode !== undefined && this.regiondata) { 29682 countryCode = number.countryCode.replace(/[wWpPtT\+#\*]/g, ''); 29683 temp = this.regiondata[countryCode]; 29684 phoneLoc = number.destinationLocale; 29685 plan = number.destinationPlan; 29686 ret.country = { 29687 sn: this.rb.getString(temp.sn).toString(), 29688 ln: this.rb.getString(temp.ln).toString(), 29689 code: phoneLoc.getRegion() 29690 }; 29691 } 29692 29693 if (!plan) { 29694 plan = this.plan; 29695 } 29696 29697 Utils.loadData({ 29698 name: "area.json", 29699 object: "PhoneGeoLocator", 29700 locale: phoneLoc, 29701 sync: sync, 29702 loadParams: JSUtils.merge(loadParams, { 29703 returnOne: true 29704 }), 29705 callback: ilib.bind(this, function (areadata) { 29706 if (areadata) { 29707 this.areadata = areadata; 29708 } 29709 areaResult = this._getAreaInfo(number, this.areadata, phoneLoc, plan, options); 29710 ret = JSUtils.merge(ret, areaResult); 29711 29712 if (ret.country === undefined) { 29713 countryCode = number.locale._mapRegiontoCC(region); 29714 29715 if (countryCode !== "0" && this.regiondata) { 29716 temp = this.regiondata[countryCode]; 29717 if (temp && temp.sn) { 29718 ret.country = { 29719 sn: this.rb.getString(temp.sn).toString(), 29720 ln: this.rb.getString(temp.ln).toString(), 29721 code: this.locale.getRegion() 29722 }; 29723 } 29724 } 29725 } 29726 }) 29727 }); 29728 29729 return ret; 29730 }, 29731 29732 /** 29733 * Returns a string that describes the ISO-3166-2 country code of the given phone 29734 * number.<p> 29735 * 29736 * If the phone number is a local phone number and does not contain 29737 * any country information, this routine will return the region for the current 29738 * formatter instance. 29739 * 29740 * @param {PhoneNumber} number An PhoneNumber instance 29741 * @return {string} 29742 */ 29743 country: function(number) { 29744 var countryCode, 29745 region, 29746 phoneLoc; 29747 29748 if (!number || !(number instanceof PhoneNumber)) { 29749 return ""; 29750 } 29751 29752 phoneLoc = number.locale; 29753 29754 region = (number.countryCode && phoneLoc._mapCCtoRegion(number.countryCode)) || 29755 (number.locale && number.locale.region) || 29756 phoneLoc.locale.getRegion() || 29757 this.locale.getRegion(); 29758 29759 countryCode = number.countryCode || phoneLoc._mapRegiontoCC(region); 29760 29761 if (number.areaCode) { 29762 region = phoneLoc._mapAreatoRegion(countryCode, number.areaCode); 29763 } else if (countryCode === "33" && number.serviceCode) { 29764 // french departments are in the service code, not the area code 29765 region = phoneLoc._mapAreatoRegion(countryCode, number.serviceCode); 29766 } 29767 return region; 29768 } 29769 }; 29770 29771 29772 /*< Measurement.js */ 29773 /* 29774 * Measurement.js - Measurement unit superclass 29775 * 29776 * Copyright © 2014-2015, 2018 JEDLSoft 29777 * 29778 * Licensed under the Apache License, Version 2.0 (the "License"); 29779 * you may not use this file except in compliance with the License. 29780 * You may obtain a copy of the License at 29781 * 29782 * http://www.apache.org/licenses/LICENSE-2.0 29783 * 29784 * Unless required by applicable law or agreed to in writing, software 29785 * distributed under the License is distributed on an "AS IS" BASIS, 29786 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 29787 * 29788 * See the License for the specific language governing permissions and 29789 * limitations under the License. 29790 */ 29791 29792 // !depends JSUtils.js MathUtils.js Locale.js 29793 29794 29795 function round(number, precision) { 29796 var factor = Math.pow(10, precision); 29797 return MathUtils.halfdown(number * factor) / factor; 29798 } 29799 29800 /** 29801 * @class 29802 * Superclass for measurement instances that contains shared functionality 29803 * and defines the interface. <p> 29804 * 29805 * This class is never instantiated on its own. Instead, measurements should 29806 * be created using the {@link MeasurementFactory} function, which creates the 29807 * correct subclass based on the given parameters.<p> 29808 * 29809 * @param {Object=} options options controlling the construction of this instance 29810 * @private 29811 * @constructor 29812 */ 29813 var Measurement = function(options) { 29814 if (options) { 29815 if (typeof(options.unit) !== 'undefined') { 29816 this.originalUnit = options.unit; 29817 this.unit = this.normalizeUnits(options.unit) || options.unit; 29818 } 29819 29820 if (typeof(options.amount) === 'object') { 29821 if (options.amount.getMeasure() === this.getMeasure()) { 29822 this.amount = options.amount.convert(this.unit); 29823 } else { 29824 throw "Cannot convert unit " + options.amount.unit + " to a " + this.getMeasure(); 29825 } 29826 } else if (typeof(options.amount) !== 'undefined') { 29827 this.amount = Number(options.amount); 29828 } 29829 29830 if (typeof(this.ratios[this.unit]) === 'undefined') { 29831 throw "Unknown unit: " + options.unit; 29832 } 29833 } 29834 }; 29835 29836 /** 29837 * @private 29838 */ 29839 Measurement._constructors = {}; 29840 29841 Measurement.prototype = { 29842 /** 29843 * Return the normalized name of the given units. If the units are 29844 * not recognized, this method returns its parameter unmodified.<p> 29845 * 29846 * Examples: 29847 * 29848 * <ui> 29849 * <li>"metres" gets normalized to "meter"<br> 29850 * <li>"ml" gets normalized to "milliliter"<br> 29851 * <li>"foobar" gets normalized to "foobar" (no change because it is not recognized) 29852 * </ul> 29853 * 29854 * @param {string} name name of the units to normalize. 29855 * @returns {string} normalized name of the units 29856 */ 29857 normalizeUnits: function(name) { 29858 return (this.constructor && (Measurement.getUnitId(this.constructor, name) || 29859 Measurement.getUnitIdCaseInsensitive(this.constructor, name))) || 29860 name; 29861 }, 29862 29863 /** 29864 * Return the normalized units used in this measurement. 29865 * @return {string} name of the unit of measurement 29866 */ 29867 getUnit: function() { 29868 return this.unit; 29869 }, 29870 29871 /** 29872 * Return the units originally used to construct this measurement 29873 * before it was normalized. 29874 * @return {string} name of the unit of measurement 29875 */ 29876 getOriginalUnit: function() { 29877 return this.originalUnit; 29878 }, 29879 29880 /** 29881 * Return the numeric amount of this measurement. 29882 * @return {number} the numeric amount of this measurement 29883 */ 29884 getAmount: function() { 29885 return this.amount; 29886 }, 29887 29888 /** 29889 * Return the type of this measurement. Examples are "mass", 29890 * "length", "speed", etc. Measurements can only be converted 29891 * to measurements of the same type.<p> 29892 * 29893 * The type of the units is determined automatically from the 29894 * units. For example, the unit "grams" is type "mass". Use the 29895 * static call {@link Measurement.getAvailableUnits} 29896 * to find out what units this version of ilib supports. 29897 * 29898 * @return {string} the name of the type of this measurement 29899 */ 29900 getMeasure: function() {}, 29901 29902 /** 29903 * Return an array of all units that this measurement types supports. 29904 * 29905 * @return {Array.<string>} an array of all units that this measurement 29906 * types supports 29907 */ 29908 getMeasures: function () { 29909 return Object.keys(this.ratios); 29910 }, 29911 29912 /** 29913 * Return the name of the measurement system that the current 29914 * unit is a part of. 29915 * 29916 * @returns {string} the name of the measurement system for 29917 * the units of this measurement 29918 */ 29919 getMeasurementSystem: function() { 29920 if (this.unit) { 29921 if (JSUtils.indexOf(this.systems.uscustomary, this.unit) > -1) { 29922 return "uscustomary"; 29923 } 29924 29925 if (JSUtils.indexOf(this.systems.imperial, this.unit) > -1) { 29926 return "imperial"; 29927 } 29928 } 29929 return "metric"; 29930 }, 29931 29932 /** 29933 * Localize the measurement to the commonly used measurement in that locale. For example 29934 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 29935 * the formatted number should be automatically converted to the most appropriate 29936 * measure in the other system, in this case, mph. The formatted result should 29937 * appear as "37.3 mph". 29938 * 29939 * @param {string} locale current locale string 29940 * @returns {Measurement} a new instance that is converted to locale 29941 */ 29942 localize: function(locale) { 29943 var to; 29944 var toSystem = Measurement.getMeasurementSystemForLocale(locale); 29945 var fromSystem = this.getMeasurementSystem(); 29946 if (toSystem === fromSystem) return this; // already there 29947 to = this.systems.conversions[fromSystem] && 29948 this.systems.conversions[fromSystem][toSystem] && 29949 this.systems.conversions[fromSystem][toSystem][this.unit]; 29950 29951 return to ? this.newUnit({ 29952 unit: to, 29953 amount: this.convert(to) 29954 }) : this; 29955 }, 29956 29957 /** 29958 * Return the amount of the current measurement when converted to the 29959 * given measurement unit. Measurements can only be converted 29960 * to other measurements of the same type.<p> 29961 * 29962 * @param {string} to the name of the units to convert this measurement to 29963 * @return {number|undefined} the amount corresponding to the requested unit 29964 */ 29965 convert: function(to) { 29966 if (!to || typeof(this.ratios[this.normalizeUnits(to)]) === 'undefined') { 29967 return undefined; 29968 } 29969 29970 var from = this.getUnitIdCaseInsensitive(this.unit) || this.unit; 29971 to = this.getUnitIdCaseInsensitive(to) || to; 29972 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 29973 return undefined; 29974 } 29975 29976 var fromRow = this.ratios[from]; 29977 var toRow = this.ratios[to]; 29978 return this.amount * fromRow[toRow[0]]; 29979 }, 29980 29981 /** 29982 * Return a new measurement instance that is converted to a different 29983 * measurement system. Measurements can only be converted 29984 * to other measurements of the same type.<p> 29985 * 29986 * @param {string} measurementSystem the name of the system to convert to 29987 * @return {Measurement} a new measurement in the given system, or the 29988 * current measurement if it is already in the given system or could not 29989 * be converted 29990 */ 29991 convertSystem: function(measurementSystem) { 29992 if (!measurementSystem || measurementSystem === this.getMeasurementSystem()) { 29993 return this; 29994 } 29995 var map = this.systems.conversions[this.getMeasurementSystem()][measurementSystem]; 29996 var newunit = map && map[this.unit]; 29997 if (!newunit) return this; 29998 29999 return this.newUnit({ 30000 unit: newunit, 30001 amount: this.convert(newunit) 30002 }); 30003 }, 30004 30005 /** 30006 * Scale the measurement unit to an acceptable level. The scaling 30007 * happens so that the integer part of the amount is as small as 30008 * possible without being below zero. This will result in the 30009 * largest units that can represent this measurement without 30010 * fractions. Measurements can only be scaled to other measurements 30011 * of the same type. 30012 * 30013 * @param {string=} measurementsystem the name of the system to scale to 30014 * @param {Object=} units mapping from the measurement system to the units to use 30015 * for this scaling. If this is not defined, this measurement type will use the 30016 * set of units that it knows about for the given measurement system 30017 * @return {Measurement} a new instance that is scaled to the 30018 * right level 30019 */ 30020 scale: function(measurementsystem, units) { 30021 var systemName = this.getMeasurementSystem(); 30022 var mSystem; 30023 if (units) { 30024 mSystem = (units[measurementsystem] && JSUtils.indexOf(units[measurementsystem], this.unit) > -1) ? 30025 units[measurementsystem] : units[systemName]; 30026 } 30027 if (!mSystem) { 30028 mSystem = (this.systems[measurementsystem] && JSUtils.indexOf(this.systems[measurementsystem], this.unit) > -1) ? 30029 this.systems[measurementsystem] : this.systems[systemName]; 30030 } 30031 if (!mSystem) { 30032 // cannot find the system to scale within... just return the measurement as is 30033 return this; 30034 } 30035 30036 return this.newUnit(this.scaleUnits(mSystem)); 30037 }, 30038 30039 /** 30040 * Expand the current measurement such that any fractions of the current unit 30041 * are represented in terms of smaller units in the same system instead of fractions 30042 * of the current unit. For example, "6.25 feet" may be represented as 30043 * "6 feet 4 inches" instead. The return value is an array of measurements which 30044 * are progressively smaller until the smallest unit in the system is reached 30045 * or until there is a whole number of any unit along the way. 30046 * 30047 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30048 * or undefined if the system can be inferred from the current measure 30049 * @param {Array.<string>=} units object containing a mapping between the measurement system 30050 * and an array of units to use to restrict the expansion to 30051 * @param {function(number):number=} constrain a function that constrains 30052 * a number according to the display options 30053 * @param {boolean=} scale if true, rescale all of the units so that the 30054 * largest unit is the largest one with a non-fractional number. If false, then 30055 * the current unit stays the largest unit. 30056 * @return {Array.<Measurement>} an array of new measurements in order from 30057 * the current units to the smallest units in the system which together are the 30058 * same measurement as this one 30059 */ 30060 expand: function(measurementsystem, units, constrain, scale) { 30061 var systemName = this.getMeasurementSystem(); 30062 var mSystem = (units && units[systemName]) ? units[systemName] : (this.systems[systemName] || this.systems.metric); 30063 30064 return this.list(mSystem, this.ratios, constrain, scale).map(function(item) { 30065 return this.newUnit(item); 30066 }.bind(this)); 30067 }, 30068 30069 /** 30070 * Convert the current measurement to a list of measures 30071 * and amounts. This method will autoScale the current measurement 30072 * to the largest measure in the given measures list such that the 30073 * amount of that measure is still greater than or equal to 1. From 30074 * there, it will truncate that measure to a whole 30075 * number and then it will calculate the remainder in terms of 30076 * each of the smaller measures in the given list.<p> 30077 * 30078 * For example, if a person's height is given as 70.5 inches, and 30079 * the list of measures is ["mile", "foot", "inch"], then it will 30080 * scale the amount to 5 feet, 10.5 inches. The amount is not big 30081 * enough to have any whole miles, so that measure is not used. 30082 * The first measure will be "foot" because it is the first one 30083 * in the measure list where the there is an amount of them that 30084 * is greater than or equal to 1. The return value in this example 30085 * would be: 30086 * 30087 * <pre> 30088 * [ 30089 * { 30090 * "unit": "foot", 30091 * "amount": 5 30092 * }, 30093 * { 30094 * "unit": "inch", 30095 * "amount": 10.5 30096 * } 30097 * ] 30098 * </pre> 30099 * 30100 * Note that all measures except the smallest will be returned 30101 * as whole numbers. The smallest measure will contain any possible 30102 * fractional remainder. 30103 * 30104 * @param {Array.<string>|undefined} measures array of measure names to 30105 * convert this measure to 30106 * @param {Object} ratios the conversion ratios 30107 * table for the measurement type 30108 * @param {function (number): number=} constrain a function that constrains 30109 * a number according to the display options 30110 * @param {boolean=} scale if true, rescale all of the units so that the 30111 * largest unit is the largest one with a non-fractional number. If false, then 30112 * the current unit stays the largest unit. 30113 * @returns {Array.<{unit: String, amount: Number}>} the conversion 30114 * of the current measurement into an array of unit names and 30115 * their amounts 30116 */ 30117 list: function(measures, ratios, constrain, scale) { 30118 var row = ratios[this.unit]; 30119 var ret = []; 30120 var scaled; 30121 var unit = this.unit; 30122 var amount = this.amount; 30123 constrain = constrain || round; 30124 30125 var start = JSUtils.indexOf(measures, this.unit); 30126 30127 if (scale || start === -1) { 30128 start = measures.length-1; 30129 } 30130 30131 if (this.unit !== measures[0]) { 30132 // if this unit is not the smallest measure in the system, we have to convert 30133 unit = measures[0]; 30134 amount = this.amount * row[ratios[unit][0]]; 30135 row = ratios[unit]; 30136 } 30137 30138 // convert to smallest measure 30139 amount = constrain(amount); 30140 // go backwards so we get from the largest to the smallest units in order 30141 for (var j = start; j > 0; j--) { 30142 unit = measures[j]; 30143 scaled = amount * row[ratios[unit][0]]; 30144 var xf = Math.floor(scaled); 30145 if (xf) { 30146 var item = { 30147 unit: unit, 30148 amount: xf 30149 }; 30150 ret.push(item); 30151 30152 amount -= xf * ratios[unit][ratios[measures[0]][0]]; 30153 } 30154 } 30155 30156 // last measure is rounded/constrained, not truncated 30157 if (amount !== 0) { 30158 ret.push({ 30159 unit: measures[0], 30160 amount: constrain(amount) 30161 }); 30162 } 30163 30164 return ret; 30165 }, 30166 30167 /** 30168 * @private 30169 */ 30170 scaleUnits: function(mSystem) { 30171 var tmp, munit, amount = 18446744073709551999; 30172 var fromRow = this.ratios[this.unit]; 30173 30174 for (var m = 0; m < mSystem.length; m++) { 30175 tmp = this.amount * fromRow[this.ratios[mSystem[m]][0]]; 30176 if ((tmp >= 1 && tmp < amount) || amount === 18446744073709551999) { 30177 amount = tmp; 30178 munit = mSystem[m]; 30179 } 30180 } 30181 30182 return { 30183 unit: munit, 30184 amount: amount 30185 }; 30186 }, 30187 30188 /** 30189 * @private 30190 * 30191 * Return the normalized units identifier for the given unit. This looks up the units 30192 * in the aliases list and returns the normalized unit id. 30193 * 30194 * @param {string} unit the unit to find 30195 * @returns {string|undefined} the normalized identifier for the given unit, or 30196 * undefined if there is no such unit in this type of measurement 30197 */ 30198 getUnitId: function(unit) { 30199 if (!unit) return undefined; 30200 30201 if (this.aliases && typeof(this.aliases[unit]) !== 'undefined') { 30202 return this.aliases[unit]; 30203 } 30204 30205 if (this.ratios && typeof(this.ratios[unit]) !== 'undefined') { 30206 return unit; 30207 } 30208 30209 return undefined; 30210 }, 30211 30212 /** 30213 * Return the normalized units identifier for the given unit, searching case-insensitively. 30214 * This has the risk that things may match erroneously because many short form unit strings 30215 * are case-sensitive. This should method be used as a last resort if no case-sensitive match 30216 * is found amongst all the different types of measurements. 30217 * 30218 * @param {string} unit the unit to find 30219 * @returns {string|undefined} the normalized identifier for the given unit, or 30220 * undefined if there is no such unit in this type of measurement 30221 */ 30222 getUnitIdCaseInsensitive: function(unit) { 30223 if (!unit) return undefined; 30224 30225 // try with the original case first, just in case that works 30226 var ret = this.getUnitId(unit); 30227 if (ret) return ret; 30228 30229 var u = unit.toLowerCase(); 30230 if (this.aliasesLower && typeof(this.aliasesLower[u]) !== 'undefined') { 30231 return this.aliasesLower[u]; 30232 } 30233 30234 return undefined; 30235 } 30236 }; 30237 30238 /** 30239 * Return the normalized units identifier for the given unit. This looks up the units 30240 * in the aliases list and returns the normalized unit id. 30241 * 30242 * @static 30243 * @param {function(...)} measurement name of the the class of measure being searched 30244 * @param {string} unit the unit to find 30245 * @returns {string|undefined} the normalized identifier for the given unit, or 30246 * undefined if there is no such unit in this type of measurement 30247 */ 30248 Measurement.getUnitId = function(measurement, unit) { 30249 if (!unit) return undefined; 30250 30251 if (typeof(measurement.aliases[unit]) !== 'undefined') { 30252 return measurement.aliases[unit]; 30253 } 30254 30255 if (measurement.ratios && typeof(measurement.ratios[unit]) !== 'undefined') { 30256 return unit; 30257 } 30258 30259 return undefined; 30260 }; 30261 30262 /** 30263 * Return the normalized units identifier for the given unit, searching case-insensitively. 30264 * This has the risk that things may match erroneously because many short form unit strings 30265 * are case-sensitive. This should method be used as a last resort if no case-sensitive match 30266 * is found amongst all the different types of measurements. 30267 * 30268 * @static 30269 * @param {function(...)} measurement name of the class of measure being searched 30270 * @param {string} unit the unit to find 30271 * @returns {string|undefined} the normalized identifier for the given unit, or 30272 * undefined if there is no such unit in this type of measurement 30273 */ 30274 Measurement.getUnitIdCaseInsensitive = function(measurement, unit) { 30275 if (!unit) return undefined; 30276 var u = unit.toLowerCase(); 30277 30278 // try this first, just in case 30279 var ret = Measurement.getUnitId(measurement, unit); 30280 if (ret) return ret; 30281 30282 if (measurement.aliases && !measurement.aliasesLower) { 30283 measurement.aliasesLower = {}; 30284 for (var a in measurement.aliases) { 30285 measurement.aliasesLower[a.toLowerCase()] = measurement.aliases[a]; 30286 } 30287 } 30288 30289 if (typeof(measurement.aliasesLower[u]) !== 'undefined') { 30290 return measurement.aliasesLower[u]; 30291 } 30292 30293 return undefined; 30294 }; 30295 30296 // Hard-code these because CLDR has incorrect data, plus this is small so we don't 30297 // want to do an async load just to get it. 30298 // Source: https://en.wikipedia.org/wiki/Metrication#Overview 30299 var systems = { 30300 "uscustomary": ["US", "FM", "MH", "LR", "PR", "PW", "GU", "WS", "AS", "VI", "MP"], 30301 "imperial": ["GB", "MM"] 30302 }; 30303 30304 // every other country in the world is metric. Myanmar (MM) is adopting metric by 2019 30305 // supposedly, and Liberia is as well 30306 30307 /** 30308 * Return the name of the measurement system in use in the given locale. 30309 * 30310 * @param {string|Locale} locale the locale spec or Locale instance of the 30311 * 30312 * @returns {string} the name of the measurement system 30313 */ 30314 Measurement.getMeasurementSystemForLocale = function(locale) { 30315 var l = typeof(locale) === "object" ? locale : new Locale(locale); 30316 var region = l.getRegion(); 30317 30318 if (region) { 30319 if (JSUtils.indexOf(systems.uscustomary, region) > -1) { 30320 return "uscustomary"; 30321 } else if (JSUtils.indexOf(systems.imperial, region) > -1) { 30322 return "imperial"; 30323 } 30324 } 30325 30326 return "metric"; 30327 }; 30328 30329 30330 30331 /*< UnknownUnit.js */ 30332 /* 30333 * Unknown.js - Dummy unit conversions for unknown types 30334 * 30335 * Copyright © 2014-2015, JEDLSoft 30336 * 30337 * Licensed under the Apache License, Version 2.0 (the "License"); 30338 * you may not use this file except in compliance with the License. 30339 * You may obtain a copy of the License at 30340 * 30341 * http://www.apache.org/licenses/LICENSE-2.0 30342 * 30343 * Unless required by applicable law or agreed to in writing, software 30344 * distributed under the License is distributed on an "AS IS" BASIS, 30345 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30346 * 30347 * See the License for the specific language governing permissions and 30348 * limitations under the License. 30349 */ 30350 30351 // !depends Measurement.js 30352 30353 30354 /** 30355 * @class 30356 * Create a new unknown measurement instance. 30357 * 30358 * @constructor 30359 * @extends Measurement 30360 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30361 * the construction of this instance 30362 */ 30363 var UnknownUnit = function (options) { 30364 30365 this.ratios = {}; 30366 this.aliases = UnknownUnit.aliases; 30367 this.aliasesLower = UnknownUnit.aliases; 30368 this.systems = UnknownUnit.systems; 30369 30370 if (options) { 30371 this.unit = options.unit; 30372 this.amount = options.amount; 30373 } 30374 }; 30375 30376 UnknownUnit.prototype = new Measurement(); 30377 UnknownUnit.prototype.parent = Measurement; 30378 UnknownUnit.prototype.constructor = UnknownUnit; 30379 30380 UnknownUnit.systems = { 30381 "metric": [], 30382 "uscustomary": [], 30383 "imperial": [], 30384 "conversions": { 30385 "metric": {}, 30386 "uscustomary": {}, 30387 "imperial": {} 30388 } 30389 }; 30390 30391 UnknownUnit.aliases = { 30392 "unknown":"unknown" 30393 }; 30394 30395 /** 30396 * Return the type of this measurement. Examples are "mass", 30397 * "length", "speed", etc. Measurements can only be converted 30398 * to measurements of the same type.<p> 30399 * 30400 * The type of the units is determined automatically from the 30401 * units. For example, the unit "grams" is type "mass". Use the 30402 * static call {@link Measurement.getAvailableUnits} 30403 * to find out what units this version of ilib supports. 30404 * 30405 * @return {string} the name of the type of this measurement 30406 */ 30407 UnknownUnit.prototype.getMeasure = function() { 30408 return "unknown"; 30409 }; 30410 30411 /** 30412 * Return a new measurement instance that is converted to a new 30413 * measurement unit. Measurements can only be converted 30414 * to measurements of the same type.<p> 30415 * 30416 * @param {string} to The name of the units to convert to 30417 * @return {number|undefined} the converted measurement 30418 * or undefined if the requested units are for a different 30419 * measurement type 30420 */ 30421 UnknownUnit.prototype.convert = function(to) { 30422 return undefined; 30423 }; 30424 30425 /** 30426 * Convert a unknown to another measure. 30427 * @static 30428 * @param {string} to unit to convert to 30429 * @param {string} from unit to convert from 30430 * @param {number} unknown amount to be convert 30431 * @returns {number|undefined} the converted amount 30432 */ 30433 UnknownUnit.convert = function(to, from, unknown) { 30434 return undefined; 30435 }; 30436 30437 /** 30438 * Localize the measurement to the commonly used measurement in that locale. For example 30439 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30440 * the formatted number should be automatically converted to the most appropriate 30441 * measure in the other system, in this case, mph. The formatted result should 30442 * appear as "37.3 mph". 30443 * 30444 * @param {string} locale current locale string 30445 * @returns {Measurement} a new instance that is converted to locale 30446 */ 30447 UnknownUnit.prototype.localize = function(locale) { 30448 return new UnknownUnit({ 30449 unit: this.unit, 30450 amount: this.amount 30451 }); 30452 }; 30453 30454 /** 30455 * Scale the measurement unit to an acceptable level. The scaling 30456 * happens so that the integer part of the amount is as small as 30457 * possible without being below zero. This will result in the 30458 * largest units that can represent this measurement without 30459 * fractions. Measurements can only be scaled to other measurements 30460 * of the same type. 30461 * 30462 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30463 * or undefined if the system can be inferred from the current measure 30464 * @return {Measurement} a new instance that is scaled to the 30465 * right level 30466 */ 30467 UnknownUnit.prototype.scale = function(measurementsystem) { 30468 return new UnknownUnit({ 30469 unit: this.unit, 30470 amount: this.amount 30471 }); 30472 }; 30473 30474 /** 30475 * Expand the current measurement such that any fractions of the current unit 30476 * are represented in terms of smaller units in the same system instead of fractions 30477 * of the current unit. For example, "6.25 feet" may be represented as 30478 * "6 feet 4 inches" instead. The return value is an array of measurements which 30479 * are progressively smaller until the smallest unit in the system is reached 30480 * or until there is a whole number of any unit along the way. 30481 * 30482 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30483 * or undefined if the system can be inferred from the current measure 30484 * @return {Array.<Measurement>} an array of new measurements in order from 30485 * the current units to the smallest units in the system which together are the 30486 * same measurement as this one 30487 */ 30488 UnknownUnit.prototype.expand = function(measurementsystem) { 30489 return [this]; // nothing to expand 30490 } 30491 30492 /** 30493 * @private 30494 * @static 30495 */ 30496 UnknownUnit.getMeasures = function () { 30497 return []; 30498 }; 30499 30500 30501 /*< AreaUnit.js */ 30502 /* 30503 * AreaUnit.js - Unit conversions for area 30504 * 30505 * Copyright © 2014-2015, 2018 JEDLSoft 30506 * 30507 * Licensed under the Apache License, Version 2.0 (the "License"); 30508 * you may not use this file except in compliance with the License. 30509 * You may obtain a copy of the License at 30510 * 30511 * http://www.apache.org/licenses/LICENSE-2.0 30512 * 30513 * Unless required by applicable law or agreed to in writing, software 30514 * distributed under the License is distributed on an "AS IS" BASIS, 30515 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30516 * 30517 * See the License for the specific language governing permissions and 30518 * limitations under the License. 30519 */ 30520 30521 /* 30522 !depends 30523 Measurement.js 30524 */ 30525 30526 30527 /** 30528 * @class 30529 * Create a new area measurement instance. 30530 * @constructor 30531 * @extends Measurement 30532 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30533 * the construction of this instance 30534 */ 30535 var AreaUnit = function (options) { 30536 this.unit = "square-meter"; 30537 this.amount = 0; 30538 30539 this.ratios = AreaUnit.ratios; 30540 this.aliases = AreaUnit.aliases; 30541 this.aliasesLower = AreaUnit.aliasesLower; 30542 this.systems = AreaUnit.systems; 30543 30544 this.parent.call(this, options); 30545 }; 30546 30547 AreaUnit.prototype = new Measurement(); 30548 AreaUnit.prototype.parent = Measurement; 30549 AreaUnit.prototype.constructor = AreaUnit; 30550 30551 AreaUnit.ratios = { 30552 /* index square cm, square meter, hectare, square km, , square inch square foot, square yard, acre, square mile */ 30553 "square-centimeter":[1, 1, 0.0001, 1e-8, 1e-10, 0.15500031, 0.00107639104, 0.000119599005, 2.47105381e-8, 3.86102159e-11 ], 30554 "square-meter": [2, 10000, 1, 1e-4, 1e-6, 1550, 10.7639, 1.19599, 0.000247105, 3.861e-7 ], 30555 "hectare": [3, 100000000, 10000, 1, 0.01, 1.55e+7, 107639, 11959.9, 2.47105 , 0.00386102 ], 30556 "square-kilometer": [4, 10000000000, 1e+6, 100, 1, 1.55e+9, 1.076e+7, 1.196e+6, 247.105 , 0.386102 ], 30557 "square-inch": [5, 6.4516, 0.00064516, 6.4516e-8, 6.4516e-10, 1, 0.0069444444444444, 0.0007716051, 1.5942e-7, 2.491e-10 ], 30558 "square-foot": [6, 929.0304, 0.092903, 9.2903e-6, 9.2903e-8, 144, 1, 0.111111, 2.2957e-5, 3.587e-8 ], 30559 "square-yard": [7, 8361.2736, 0.836127, 8.3613e-5, 8.3613e-7, 1296, 9, 1, 0.000206612, 3.2283e-7 ], 30560 "acre": [8, 40468564.2, 4046.86, 0.404686, 0.00404686, 6.273e+6, 43560, 4840, 1, 0.0015625 ], 30561 "square-mile": [9, 2.58998811e+10, 2.59e+6, 258.999, 2.58999, 4.014e+9, 2.788e+7, 3.098e+6, 640, 1 ] 30562 } 30563 30564 /** 30565 * Return the type of this measurement. Examples are "mass", 30566 * "length", "speed", etc. Measurements can only be converted 30567 * to measurements of the same type.<p> 30568 * 30569 * The type of the units is determined automatically from the 30570 * units. For example, the unit "grams" is type "mass". Use the 30571 * static call {@link Measurement.getAvailableUnits} 30572 * to find out what units this version of ilib supports. 30573 * 30574 * @return {string} the name of the type of this measurement 30575 */ 30576 AreaUnit.prototype.getMeasure = function() { 30577 return "area"; 30578 }; 30579 30580 /** 30581 * Return a new instance of this type of measurement. 30582 * 30583 * @param {Object} params parameters to the constructor 30584 * @return {Measurement} a measurement subclass instance 30585 */ 30586 AreaUnit.prototype.newUnit = function(params) { 30587 return new AreaUnit(params); 30588 }; 30589 30590 AreaUnit.aliases = { 30591 "square centimeter":"square-centimeter", 30592 "square centimeters":"square-centimeter", 30593 "square centimetre":"square-centimeter", 30594 "square centimetres":"square-centimeter", 30595 "sq centimeter":"square-centimeter", 30596 "sq centimeters":"square-centimeter", 30597 "sq centimetre":"square-centimeter", 30598 "sq centimetres":"square-centimeter", 30599 "square cm":"square-centimeter", 30600 "sq cm":"square-centimeter", 30601 "cm2":"square-centimeter", 30602 "cm²":"square-centimeter", 30603 "square kilometer":"square-kilometer", 30604 "square kilometre":"square-kilometer", 30605 "square kilometers":"square-kilometer", 30606 "square kilometres":"square-kilometer", 30607 "sq kilometer":"square-kilometer", 30608 "sq kilometre":"square-kilometer", 30609 "sq kilometers":"square-kilometer", 30610 "sq kilometres":"square-kilometer", 30611 "square km":"square-kilometer", 30612 "sq km":"square-kilometer", 30613 "km2":"square-kilometer", 30614 "km²":"square-kilometer", 30615 "hectare":"hectare", 30616 "ha":"hectare", 30617 "square meter": "square-meter", 30618 "square meters":"square-meter", 30619 "square metre": "square-meter", 30620 "square metres": "square-meter", 30621 "sq meter": "square-meter", 30622 "sq meters":"square-meter", 30623 "sq metre": "square-meter", 30624 "sq metres": "square-meter", 30625 "sqm":"square-meter", 30626 "m2": "square-meter", 30627 "m²":"square-meter", 30628 "square mile":"square-mile", 30629 "square miles":"square-mile", 30630 "square mi":"square-mile", 30631 "sq mi":"square-mile", 30632 "mi2":"square-mile", 30633 "mi²":"square-mile", 30634 "acre": "acre", 30635 "acres":"acre", 30636 "square yard": "square-yard", 30637 "square yards":"square-yard", 30638 "sq yard": "square-yard", 30639 "sq yards": "square-yard", 30640 "sq yrd": "square-yard", 30641 "sq yrds": "square-yard", 30642 "yard2":"square-yard", 30643 "yard²":"square-yard", 30644 "yrd2":"square-yard", 30645 "yrd²":"square-yard", 30646 "yd2":"square-yard", 30647 "yd²":"square-yard", 30648 "square foot": "square-foot", 30649 "square feet": "square-foot", 30650 "sq ft":"square-foot", 30651 "ft2":"square-foot", 30652 "ft²":"square-foot", 30653 "square inch":"square-inch", 30654 "square inches":"square-inch", 30655 "in2":"square-inch", 30656 "in²":"square-inch" 30657 }; 30658 30659 (function() { 30660 AreaUnit.aliasesLower = {}; 30661 for (var a in AreaUnit.aliases) { 30662 AreaUnit.aliasesLower[a.toLowerCase()] = AreaUnit.aliases[a]; 30663 } 30664 })(); 30665 30666 /** 30667 * Convert a Area to another measure. 30668 * @static 30669 * @param to {string} unit to convert to 30670 * @param from {string} unit to convert from 30671 * @param area {number} amount to be convert 30672 * @returns {number|undefined} the converted amount 30673 */ 30674 AreaUnit.convert = function(to, from, area) { 30675 from = Measurement.getUnitIdCaseInsensitive(AreaUnit, from) || from; 30676 to = Measurement.getUnitIdCaseInsensitive(AreaUnit, to) || to; 30677 var fromRow = AreaUnit.ratios[from]; 30678 var toRow = AreaUnit.ratios[to]; 30679 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 30680 return undefined; 30681 } 30682 return area* fromRow[toRow[0]]; 30683 }; 30684 30685 /** 30686 * @private 30687 * @static 30688 */ 30689 AreaUnit.getMeasures = function () { 30690 return Object.keys(AreaUnit.ratios); 30691 }; 30692 30693 AreaUnit.systems = { 30694 "metric": [ 30695 "square-centimeter", 30696 "square-meter", 30697 "hectare", 30698 "square-kilometer" 30699 ], 30700 "imperial": [ 30701 "square-inch", 30702 "square-foot", 30703 "square-yard", 30704 "acre", 30705 "square-mile" 30706 ], 30707 "uscustomary": [ 30708 "square-inch", 30709 "square-foot", 30710 "square-yard", 30711 "acre", 30712 "square-mile" 30713 ], 30714 "conversions": { 30715 "metric": { 30716 "uscustomary": { 30717 "square-centimeter" : "square-inch", 30718 "square-meter" : "square-yard", 30719 "hectare" : "acre", 30720 "square-kilometer" : "square-mile" 30721 }, 30722 "imperial": { 30723 "square-centimeter" : "square-inch", 30724 "square-meter" : "square-yard", 30725 "hectare" : "acre", 30726 "square-kilometer" : "square-mile" 30727 } 30728 }, 30729 "uscustomary": { 30730 "metric": { 30731 "square-inch" : "square-centimeter", 30732 "square-foot" : "square-meter", 30733 "square-yard" : "square-meter", 30734 "acre" : "hectare", 30735 "square-mile" : "square-kilometer" 30736 } 30737 }, 30738 "imperial": { 30739 "metric": { 30740 "square-inch" : "square-centimeter", 30741 "square-foot" : "square-meter", 30742 "square-yard" : "square-meter", 30743 "acre" : "hectare", 30744 "square-mile" : "square-kilometer" 30745 } 30746 } 30747 } 30748 }; 30749 30750 //register with the factory method 30751 Measurement._constructors["area"] = AreaUnit; 30752 30753 30754 /*< DigitalStorageUnit.js */ 30755 /* 30756 * DigitalStorageUnit.js - Unit conversions for Digital Storage 30757 * 30758 * Copyright © 2014-2015, 2018 JEDLSoft 30759 * 30760 * Licensed under the Apache License, Version 2.0 (the "License"); 30761 * you may not use this file except in compliance with the License. 30762 * You may obtain a copy of the License at 30763 * 30764 * http://www.apache.org/licenses/LICENSE-2.0 30765 * 30766 * Unless required by applicable law or agreed to in writing, software 30767 * distributed under the License is distributed on an "AS IS" BASIS, 30768 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 30769 * 30770 * See the License for the specific language governing permissions and 30771 * limitations under the License. 30772 */ 30773 30774 /* 30775 !depends 30776 Measurement.js 30777 JSUtils.js 30778 */ 30779 30780 30781 /** 30782 * @class 30783 * Create a new DigitalStorage measurement instance. 30784 * 30785 * @constructor 30786 * @extends Measurement 30787 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 30788 * the construction of this instance 30789 */ 30790 var DigitalStorageUnit = function (options) { 30791 this.unit = "byte"; 30792 this.amount = 0; 30793 30794 this.ratios = DigitalStorageUnit.ratios; 30795 this.aliases = DigitalStorageUnit.aliases; 30796 this.aliasesLower = DigitalStorageUnit.aliasesLower; 30797 this.systems = DigitalStorageUnit.systems; 30798 30799 this.parent.call(this, options); 30800 }; 30801 30802 DigitalStorageUnit.prototype = new Measurement(); 30803 DigitalStorageUnit.prototype.parent = Measurement; 30804 DigitalStorageUnit.prototype.constructor = DigitalStorageUnit; 30805 30806 DigitalStorageUnit.ratios = { 30807 /* # bit byte kb kB mb mB gb gB tb tB pb pB */ 30808 "bit": [ 1, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 8.881784197e-16, 1.110223025e-16 ], 30809 "byte": [ 2, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13, 7.105427358e-15, 8.881784197e-16 ], 30810 "kilobit": [ 3, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13 ], 30811 "kilobyte": [ 4, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13 ], 30812 "megabit": [ 5, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10 ], 30813 "megabyte": [ 6, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10 ], 30814 "gigabit": [ 7, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7 ], 30815 "gigabyte": [ 8, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7 ], 30816 "terabit": [ 9, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4 ], 30817 "terabyte": [ 10, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625 ], 30818 "petabit": [ 11, 1.125899907e15, 1.407374884e14, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125 ], 30819 "petabyte": [ 12, 9.007199255e15, 1.125899907e15, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1 ] 30820 }; 30821 30822 /** 30823 * Return a new instance of this type of measurement. 30824 * 30825 * @param {Object} params parameters to the constructor 30826 * @return {Measurement} a measurement subclass instance 30827 */ 30828 DigitalStorageUnit.prototype.newUnit = function(params) { 30829 return new DigitalStorageUnit(params); 30830 }; 30831 30832 DigitalStorageUnit.systems = { 30833 "metric": [], 30834 "uscustomary": [], 30835 "imperial": [], 30836 "conversions": { 30837 "metric": {}, 30838 "uscustomary": {}, 30839 "imperial": {} 30840 } 30841 }; 30842 30843 DigitalStorageUnit.bitSystem = [ 30844 "bit", 30845 "kilobit", 30846 "megabit", 30847 "gigabit", 30848 "terabit", 30849 "petabit" 30850 ]; 30851 DigitalStorageUnit.byteSystem = [ 30852 "byte", 30853 "kilobyte", 30854 "megabyte", 30855 "gigabyte", 30856 "terabyte", 30857 "petabyte" 30858 ]; 30859 30860 /** 30861 * Return the type of this measurement. Examples are "mass", 30862 * "length", "speed", etc. Measurements can only be converted 30863 * to measurements of the same type.<p> 30864 * 30865 * The type of the units is determined automatically from the 30866 * units. For example, the unit "grams" is type "mass". Use the 30867 * static call {@link Measurement.getAvailableUnits} 30868 * to find out what units this version of ilib supports. 30869 * 30870 * @return {string} the name of the type of this measurement 30871 */ 30872 DigitalStorageUnit.prototype.getMeasure = function() { 30873 return "digitalStorage"; 30874 }; 30875 30876 /** 30877 * Localize the measurement to the commonly used measurement in that locale. For example 30878 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 30879 * the formatted number should be automatically converted to the most appropriate 30880 * measure in the other system, in this case, mph. The formatted result should 30881 * appear as "37.3 mph". 30882 * 30883 * @param {string} locale current locale string 30884 * @returns {Measurement} a new instance that is converted to locale 30885 */ 30886 DigitalStorageUnit.prototype.localize = function(locale) { 30887 return new DigitalStorageUnit({ 30888 unit: this.unit, 30889 amount: this.amount 30890 }); 30891 }; 30892 30893 /** 30894 * Scale the measurement unit to an acceptable level. The scaling 30895 * happens so that the integer part of the amount is as small as 30896 * possible without being below zero. This will result in the 30897 * largest units that can represent this measurement without 30898 * fractions. Measurements can only be scaled to other measurements 30899 * of the same type. 30900 * 30901 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30902 * or undefined if the system can be inferred from the current measure 30903 * @param {Object=} units mapping from the measurement system to the units to use 30904 * for this scaling. If this is not defined, this measurement type will use the 30905 * set of units that it knows about for the given measurement system 30906 * @return {Measurement} a new instance that is scaled to the 30907 * right level 30908 */ 30909 DigitalStorageUnit.prototype.scale = function(measurementsystem, units) { 30910 var mSystem, systemName = this.getMeasurementSystem(); 30911 if (units) { 30912 mSystem = units[systemName]; 30913 } else { 30914 if (JSUtils.indexOf(DigitalStorageUnit.byteSystem, this.unit) > -1) { 30915 mSystem = DigitalStorageUnit.byteSystem; 30916 } else { 30917 mSystem = DigitalStorageUnit.bitSystem; 30918 } 30919 } 30920 30921 return this.newUnit(this.scaleUnits(mSystem)); 30922 }; 30923 30924 /** 30925 * Expand the current measurement such that any fractions of the current unit 30926 * are represented in terms of smaller units in the same system instead of fractions 30927 * of the current unit. For example, "6.25 feet" may be represented as 30928 * "6 feet 4 inches" instead. The return value is an array of measurements which 30929 * are progressively smaller until the smallest unit in the system is reached 30930 * or until there is a whole number of any unit along the way. 30931 * 30932 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 30933 * or undefined if the system can be inferred from the current measure 30934 * @param {Object=} units mapping from the measurement system to the units to use 30935 * for this scaling. If this is not defined, this measurement type will use the 30936 * set of units that it knows about for the given measurement system 30937 * @return {Array.<Measurement>} an array of new measurements in order from 30938 * the current units to the smallest units in the system which together are the 30939 * same measurement as this one 30940 */ 30941 DigitalStorageUnit.prototype.expand = function(measurementsystem, units) { 30942 var mSystem, systemName = this.getMeasurementSystem(); 30943 if (units) { 30944 mSystem = units[systemName]; 30945 } else { 30946 if (this.unit in DigitalStorageUnit.byteSystem) { 30947 mSystem = DigitalStorageUnit.byteSystem; 30948 } else { 30949 mSystem = DigitalStorageUnit.bitSystem; 30950 } 30951 } 30952 30953 return this.list(mSystem, DigitalStorageUnit.ratios).map(function(item) { 30954 return new DigitalStorageUnit(item); 30955 }); 30956 }; 30957 30958 30959 DigitalStorageUnit.aliases = { 30960 "bits": "bit", 30961 "bit": "bit", 30962 "Bits": "bit", 30963 "Bit": "bit", 30964 "byte": "byte", 30965 "bytes": "byte", 30966 "Byte": "byte", 30967 "Bytes": "byte", 30968 "kilobits": "kilobit", 30969 "Kilobits": "kilobit", 30970 "KiloBits": "kilobit", 30971 "kiloBits": "kilobit", 30972 "kilobit": "kilobit", 30973 "Kilobit": "kilobit", 30974 "kiloBit": "kilobit", 30975 "KiloBit": "kilobit", 30976 "kb": "kilobit", 30977 "Kb": "kilobit", 30978 "kilobyte": "kilobyte", 30979 "Kilobyte": "kilobyte", 30980 "kiloByte": "kilobyte", 30981 "KiloByte": "kilobyte", 30982 "kilobytes": "kilobyte", 30983 "Kilobytes": "kilobyte", 30984 "kiloBytes": "kilobyte", 30985 "KiloBytes": "kilobyte", 30986 "kB": "kilobyte", 30987 "KB": "kilobyte", 30988 "megabit": "megabit", 30989 "Megabit": "megabit", 30990 "megaBit": "megabit", 30991 "MegaBit": "megabit", 30992 "megabits": "megabit", 30993 "Megabits": "megabit", 30994 "megaBits": "megabit", 30995 "MegaBits": "megabit", 30996 "Mb": "megabit", 30997 "mb": "megabit", 30998 "megabyte": "megabyte", 30999 "Megabyte": "megabyte", 31000 "megaByte": "megabyte", 31001 "MegaByte": "megabyte", 31002 "megabytes": "megabyte", 31003 "Megabytes": "megabyte", 31004 "megaBytes": "megabyte", 31005 "MegaBytes": "megabyte", 31006 "MB": "megabyte", 31007 "mB": "megabyte", 31008 "gigabit": "gigabit", 31009 "Gigabit": "gigabit", 31010 "gigaBit": "gigabit", 31011 "GigaBit": "gigabit", 31012 "gigabits": "gigabit", 31013 "Gigabits": "gigabit", 31014 "gigaBits": "gigabyte", 31015 "GigaBits": "gigabit", 31016 "Gb": "gigabit", 31017 "gb": "gigabit", 31018 "gigabyte": "gigabyte", 31019 "Gigabyte": "gigabyte", 31020 "gigaByte": "gigabyte", 31021 "GigaByte": "gigabyte", 31022 "gigabytes": "gigabyte", 31023 "Gigabytes": "gigabyte", 31024 "gigaBytes": "gigabyte", 31025 "GigaBytes": "gigabyte", 31026 "GB": "gigabyte", 31027 "gB": "gigabyte", 31028 "terabit": "terabit", 31029 "Terabit": "terabit", 31030 "teraBit": "terabit", 31031 "TeraBit": "terabit", 31032 "terabits": "terabit", 31033 "Terabits": "terabit", 31034 "teraBits": "terabit", 31035 "TeraBits": "terabit", 31036 "tb": "terabit", 31037 "Tb": "terabit", 31038 "terabyte": "terabyte", 31039 "Terabyte": "terabyte", 31040 "teraByte": "terabyte", 31041 "TeraByte": "terabyte", 31042 "terabytes": "terabyte", 31043 "Terabytes": "terabyte", 31044 "teraBytes": "terabyte", 31045 "TeraBytes": "terabyte", 31046 "TB": "terabyte", 31047 "tB": "terabyte", 31048 "petabit": "petabit", 31049 "Petabit": "petabit", 31050 "petaBit": "petabit", 31051 "PetaBit": "petabit", 31052 "petabits": "petabit", 31053 "Petabits": "petabit", 31054 "petaBits": "petabit", 31055 "PetaBits": "petabit", 31056 "pb": "petabit", 31057 "Pb": "petabit", 31058 "petabyte": "petabyte", 31059 "Petabyte": "petabyte", 31060 "petaByte": "petabyte", 31061 "PetaByte": "petabyte", 31062 "petabytes": "petabyte", 31063 "Petabytes": "petabyte", 31064 "petaBytes": "petabyte", 31065 "PetaBytes": "petabyte", 31066 "PB": "petabyte", 31067 "pB": "petabyte" 31068 }; 31069 31070 (function() { 31071 DigitalStorageUnit.aliasesLower = {}; 31072 for (var a in DigitalStorageUnit.aliases) { 31073 DigitalStorageUnit.aliasesLower[a.toLowerCase()] = DigitalStorageUnit.aliases[a]; 31074 } 31075 })(); 31076 31077 /** 31078 * Convert a digitalStorage to another measure. 31079 * @static 31080 * @param to {string} unit to convert to 31081 * @param from {string} unit to convert from 31082 * @param digitalStorage {number} amount to be convert 31083 * @returns {number|undefined} the converted amount 31084 */ 31085 DigitalStorageUnit.convert = function(to, from, digitalStorage) { 31086 from = Measurement.getUnitIdCaseInsensitive(DigitalStorageUnit, from) || from; 31087 to = Measurement.getUnitIdCaseInsensitive(DigitalStorageUnit, to) || to; 31088 var fromRow = DigitalStorageUnit.ratios[from]; 31089 var toRow = DigitalStorageUnit.ratios[to]; 31090 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31091 return undefined; 31092 } 31093 var result = digitalStorage * fromRow[toRow[0]]; 31094 return result; 31095 }; 31096 31097 /** 31098 * @private 31099 * @static 31100 */ 31101 DigitalStorageUnit.getMeasures = function () { 31102 return Object.keys(DigitalStorageUnit.ratios); 31103 }; 31104 31105 //register with the factory method 31106 Measurement._constructors["digitalStorage"] = DigitalStorageUnit; 31107 31108 31109 /*< DigitalSpeedUnit.js */ 31110 /* 31111 * DigitalSpeedUnit.js - Unit conversions for Digital Storage 31112 * 31113 * Copyright © 2018 JEDLSoft 31114 * 31115 * Licensed under the Apache License, Version 2.0 (the "License"); 31116 * you may not use this file except in compliance with the License. 31117 * You may obtain a copy of the License at 31118 * 31119 * http://www.apache.org/licenses/LICENSE-2.0 31120 * 31121 * Unless required by applicable law or agreed to in writing, software 31122 * distributed under the License is distributed on an "AS IS" BASIS, 31123 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31124 * 31125 * See the License for the specific language governing permissions and 31126 * limitations under the License. 31127 */ 31128 31129 /* 31130 !depends 31131 Measurement.js 31132 JSUtils.js 31133 */ 31134 31135 31136 /** 31137 * @class 31138 * Create a new DigitalSpeed measurement instance. This measures the speed of 31139 * transfer of data. 31140 * 31141 * @constructor 31142 * @extends Measurement 31143 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31144 * the construction of this instance 31145 */ 31146 var DigitalSpeedUnit = function (options) { 31147 this.unit = "byte"; 31148 this.amount = 0; 31149 31150 this.ratios = DigitalSpeedUnit.ratios; 31151 this.aliases = DigitalSpeedUnit.aliases; 31152 this.aliasesLower = DigitalSpeedUnit.aliasesLower; 31153 this.systems = DigitalSpeedUnit.systems; 31154 31155 this.parent.call(this, options); 31156 }; 31157 31158 DigitalSpeedUnit.prototype = new Measurement(); 31159 DigitalSpeedUnit.prototype.parent = Measurement; 31160 DigitalSpeedUnit.prototype.constructor = DigitalSpeedUnit; 31161 31162 DigitalSpeedUnit.ratios = { 31163 /* # bps Bps kbps kBps Mbps MBps Gbps GBps Tbps TBps Pbps PBps Bph kBph MBph GBph TBph PBph */ 31164 "bit-per-second": [ 1, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 8.881784197e-16, 1.110223025e-16, 450, 0.45, 4.5e-4, 4.5e-7, 4.5e-10, 4.5e-13 ], 31165 "byte-per-second": [ 2, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13, 7.105427358e-15, 8.881784197e-16, 3600, 3.6, 3.6e-3, 3.6e-6, 3.6e-9, 3.6e-12 ], 31166 "kilobit-per-second": [ 3, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 9.094947017e-13, 1.136868377e-13, 4.5e5, 450, 0.45, 4.5e-4, 4.5e-7, 4.5e-10 ], 31167 "kilobyte-per-second": [ 4, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 7.275957614e-12, 9.094947017e-13, 3.6e6, 3600, 3.6, 3.6e-3, 3.6e-6, 3.6e-9 ], 31168 "megabit-per-second": [ 5, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 9.313225746e-10, 1.164153218e-10, 4.5e8, 4.5e5, 450, 0.45, 4.5e-4, 4.5e-7 ], 31169 "megabyte-per-second": [ 6, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 7.450580597e-9, 9.313225746e-10, 3.6e9, 3.6e6, 3600, 3.6, 3.6e-3, 3.6e-6 ], 31170 "gigabit-per-second": [ 7, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 9.536743164e-7, 1.192092896e-7, 4.5e11, 4.5e8, 4.5e5, 450, 0.45, 4.5e-4 ], 31171 "gigabyte-per-second": [ 8, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 7.629394531e-6, 9.536743164e-7, 3.6e12, 3.6e9, 3.6e6, 3600, 3.6, 3.6e-3 ], 31172 "terabit-per-second": [ 9, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 0.0009765625, 1.220703125e-4, 4.5e14, 4.5e11, 4.5e8, 4.5e5, 450, 0.45 ], 31173 "terabyte-per-second": [ 10, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 0.0078125, 0.0009765625, 3.6e15, 3.6e12, 3.6e9, 3.6e6, 3600, 3.6 ], 31174 "petabit-per-second": [ 11, 1.125899907e15, 1.407374884e14, 1.099511628e12, 137438953472, 1073741824, 134217728, 1048576, 131072, 1024, 128, 1, 0.125, 4.5e17, 4.5e14, 4.5e11, 4.5e8, 4.5e5, 450 ], 31175 "petabyte-per-second": [ 12, 9.007199255e15, 1.125899907e15, 8.796093022e12, 1.099511628e12, 8589934592, 1073741824, 8388608, 1048576, 8192, 1024, 8, 1, 3.6e18, 3.6e15, 3.6e12, 3.6e9, 3.6e6, 3600 ], 31176 31177 "byte-per-hour": [ 13, 28800, 3600, 28.125, 3.515625, 0.0274658203116, 3.43322753904e-3, 2.68220901492e-5, 3.35276126856e-6, 2.61934474104e-8, 3.27418092612e-9, 2.55795384888e-11, 3.19744231092e-12, 1, 0.0078125, 9.536743164e-7, 9.313225746e-10, 9.094947017e-13, 8.881784197e-16 ], 31178 "kilobyte-per-hour": [ 14, 29491200, 3686400, 28800, 3600, 28.125, 3.515625, 0.0274658203116, 3.43322753904e-3, 2.68220901492e-5, 3.35276126856e-6, 2.61934474104e-8, 3.27418092612e-9, 1024, 1, 0.0078125, 9.536743164e-7, 9.313225746e-10, 9.094947017e-13 ], 31179 "megabyte-per-hour": [ 15, 30198988800, 3774873600, 29491200, 3686400, 28800, 3600, 28.125, 3.515625, 0.0274658203116, 3.43322753904e-3, 2.68220901492e-5, 3.35276126856e-6, 1048576, 1024, 1, 0.0078125, 9.536743164e-7, 9.313225746e-10 ], 31180 "gigabyte-per-hour": [ 16, 30923764531200, 3865470566400, 30198988800, 3774873600, 29491200, 3686400, 28800, 3600, 28.125, 3.515625, 0.0274658203116, 3.43322753904e-3, 1073741824, 1048576, 1024, 1, 0.0078125, 9.536743164e-7 ], 31181 "terabyte-per-hour": [ 17, 3.16659348792e16, 3.9582418608e16, 30923764531200, 3865470566400, 30198988800, 3774873600, 29491200, 3686400, 28800, 3600, 28.125, 3.515625, 1.099511628e12, 1073741824, 1048576, 1024, 1, 0.0078125 ], 31182 "petabyte-per-hour": [ 18, 3.2425917318e19, 4.0532396652e18, 3.16659348792e16, 3.9582418608e16, 30923764531200, 3865470566400, 30198988800, 3774873600, 29491200, 3686400, 28800, 3600, 1.125899907e15, 1.099511628e12, 1073741824, 1048576, 1024, 1 ] 31183 }; 31184 31185 /** 31186 * Return a new instance of this type of measurement. 31187 * 31188 * @param {Object} params parameters to the constructor 31189 * @return {Measurement} a measurement subclass instance 31190 */ 31191 DigitalSpeedUnit.prototype.newUnit = function(params) { 31192 return new DigitalSpeedUnit(params); 31193 }; 31194 31195 DigitalSpeedUnit.systems = { 31196 "metric": [], 31197 "uscustomary": [], 31198 "imperial": [], 31199 "conversions": { 31200 "metric": {}, 31201 "uscustomary": {}, 31202 "imperial": {} 31203 } 31204 }; 31205 31206 DigitalSpeedUnit.bitSystem = [ 31207 "bit-per-second", 31208 "kilobit-per-second", 31209 "megabit-per-second", 31210 "gigabit-per-second", 31211 "terabit-per-second", 31212 "petabit-per-second" 31213 ]; 31214 DigitalSpeedUnit.byteSystem = [ 31215 "byte-per-second", 31216 "kilobyte-per-second", 31217 "megabyte-per-second", 31218 "gigabyte-per-second", 31219 "terabyte-per-second", 31220 "petabyte-per-second" 31221 ]; 31222 31223 /** 31224 * Return the type of this measurement. Examples are "mass", 31225 * "length", "speed", etc. Measurements can only be converted 31226 * to measurements of the same type.<p> 31227 * 31228 * The type of the units is determined automatically from the 31229 * units. For example, the unit "grams" is type "mass". Use the 31230 * static call {@link Measurement.getAvailableUnits} 31231 * to find out what units this version of ilib supports. 31232 * 31233 * @override 31234 * @return {string} the name of the type of this measurement 31235 */ 31236 DigitalSpeedUnit.prototype.getMeasure = function() { 31237 return "digitalSpeed"; 31238 }; 31239 31240 /** 31241 * Localize the measurement to the commonly used measurement in that locale. For example 31242 * If a user's locale is "en-US" and the measurement is given as "60 kmh", 31243 * the formatted number should be automatically converted to the most appropriate 31244 * measure in the other system, in this case, mph. The formatted result should 31245 * appear as "37.3 mph". 31246 * 31247 * @param {string} locale current locale string 31248 * @returns {Measurement} a new instance that is converted to locale 31249 */ 31250 DigitalSpeedUnit.prototype.localize = function(locale) { 31251 return new DigitalSpeedUnit({ 31252 unit: this.unit, 31253 amount: this.amount 31254 }); 31255 }; 31256 31257 /** 31258 * Scale the measurement unit to an acceptable level. The scaling 31259 * happens so that the integer part of the amount is as small as 31260 * possible without being below zero. This will result in the 31261 * largest units that can represent this measurement without 31262 * fractions. Measurements can only be scaled to other measurements 31263 * of the same type. 31264 * 31265 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31266 * or undefined if the system can be inferred from the current measure 31267 * @param {Object=} units mapping from the measurement system to the units to use 31268 * for this scaling. If this is not defined, this measurement type will use the 31269 * set of units that it knows about for the given measurement system 31270 * @return {Measurement} a new instance that is scaled to the 31271 * right level 31272 */ 31273 DigitalSpeedUnit.prototype.scale = function(measurementsystem, units) { 31274 var mSystem, systemName = this.getMeasurementSystem(); 31275 if (units) { 31276 mSystem = units[systemName]; 31277 } else { 31278 if (JSUtils.indexOf(DigitalSpeedUnit.byteSystem, this.unit) > -1) { 31279 mSystem = DigitalSpeedUnit.byteSystem; 31280 } else { 31281 mSystem = DigitalSpeedUnit.bitSystem; 31282 } 31283 } 31284 31285 return this.newUnit(this.scaleUnits(mSystem)); 31286 }; 31287 31288 /** 31289 * Expand the current measurement such that any fractions of the current unit 31290 * are represented in terms of smaller units in the same system instead of fractions 31291 * of the current unit. For example, "6.25 feet" may be represented as 31292 * "6 feet 4 inches" instead. The return value is an array of measurements which 31293 * are progressively smaller until the smallest unit in the system is reached 31294 * or until there is a whole number of any unit along the way. 31295 * 31296 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31297 * or undefined if the system can be inferred from the current measure 31298 * @param {Object=} units mapping from the measurement system to the units to use 31299 * for this scaling. If this is not defined, this measurement type will use the 31300 * set of units that it knows about for the given measurement system 31301 * @return {Array.<Measurement>} an array of new measurements in order from 31302 * the current units to the smallest units in the system which together are the 31303 * same measurement as this one 31304 */ 31305 DigitalSpeedUnit.prototype.expand = function(measurementsystem, units) { 31306 var mSystem, systemName = this.getMeasurementSystem(); 31307 if (units) { 31308 mSystem = units[systemName]; 31309 } else { 31310 if (this.unit in DigitalSpeedUnit.byteSystem) { 31311 mSystem = DigitalSpeedUnit.byteSystem; 31312 } else { 31313 mSystem = DigitalSpeedUnit.bitSystem; 31314 } 31315 } 31316 31317 return this.list(mSystem, DigitalSpeedUnit.ratios).map(function(item) { 31318 return new DigitalSpeedUnit(item); 31319 }); 31320 }; 31321 31322 DigitalSpeedUnit.aliases = { 31323 "bits/s": "bit-per-second", 31324 "bit/s": "bit-per-second", 31325 "bits/second": "bit-per-second", 31326 "bit/second": "bit-per-second", 31327 "bps": "bit-per-second", 31328 "byte/s": "byte-per-second", 31329 "bytes/s": "byte-per-second", 31330 "byte/second": "byte-per-second", 31331 "bytes/second": "byte-per-second", 31332 "Bps": "byte-per-second", 31333 "kilobits/s": "kilobit-per-second", 31334 "kilobits/second": "kilobit-per-second", 31335 "Kilobits/s": "kilobit-per-second", 31336 "kilobit/s": "kilobit-per-second", 31337 "kilobit/second": "kilobit-per-second", 31338 "Kilobit/s": "kilobit-per-second", 31339 "kb/s": "kilobit-per-second", 31340 "Kb/s": "kilobit-per-second", 31341 "kbps": "kilobit-per-second", 31342 "Kbps": "kilobit-per-second", 31343 "kilobyte/s": "kilobyte-per-second", 31344 "kilobyte/second": "kilobyte-per-second", 31345 "Kilobyte/s": "kilobyte-per-second", 31346 "kilobytes/s": "kilobyte-per-second", 31347 "kilobytes/second": "kilobyte-per-second", 31348 "Kilobytes/s": "kilobyte-per-second", 31349 "kB/s": "kilobyte-per-second", 31350 "KB/s": "kilobyte-per-second", 31351 "kBps": "kilobyte-per-second", 31352 "KBps": "kilobyte-per-second", 31353 "megabit/s": "megabit-per-second", 31354 "megabits/s": "megabit-per-second", 31355 "megabit/second": "megabit-per-second", 31356 "megabits/second": "megabit-per-second", 31357 "Mb/s": "megabit-per-second", 31358 "mb/s": "megabit-per-second", 31359 "mbps": "megabit-per-second", 31360 "Mbps": "megabit-per-second", 31361 "megabyte/s": "megabyte-per-second", 31362 "megabytes/s": "megabyte-per-second", 31363 "megabyte/second": "megabyte-per-second", 31364 "megabytes/second": "megabyte-per-second", 31365 "MB/s": "megabyte-per-second", 31366 "mB/s": "megabyte-per-second", 31367 "mBps": "megabyte-per-second", 31368 "MBps": "megabyte-per-second", 31369 "gigabit/s": "gigabit-per-second", 31370 "gigabits/s": "gigabit-per-second", 31371 "gigabit/second": "gigabit-per-second", 31372 "gigabits/second": "gigabit-per-second", 31373 "Gb/s": "gigabit-per-second", 31374 "gb/s": "gigabit-per-second", 31375 "gbps": "gigabit-per-second", 31376 "Gbps": "gigabit-per-second", 31377 "gigabyte/s": "gigabyte-per-second", 31378 "gigabytes/s": "gigabyte-per-second", 31379 "gigabyte/second": "gigabyte-per-second", 31380 "gigabytes/second": "gigabyte-per-second", 31381 "GB/s": "gigabyte-per-second", 31382 "gB/s": "gigabyte-per-second", 31383 "gBps": "gigabyte-per-second", 31384 "GBps": "gigabyte-per-second", 31385 "terabit/second": "terabit-per-second", 31386 "terabits/second": "terabit-per-second", 31387 "tb/s": "terabit-per-second", 31388 "Tb/s": "terabit-per-second", 31389 "tbps": "terabit-per-second", 31390 "Tbps": "terabit-per-second", 31391 "terabyte/s": "terabyte-per-second", 31392 "terabytes/s": "terabyte-per-second", 31393 "terabyte/second": "terabyte-per-second", 31394 "terabytes/second": "terabyte-per-second", 31395 "TB/s": "terabyte-per-second", 31396 "tB/s": "terabyte-per-second", 31397 "tBps": "terabyte-per-second", 31398 "TBps": "terabyte-per-second", 31399 "petabit/s": "petabit-per-second", 31400 "petabits/s": "petabit-per-second", 31401 "petabit/second": "petabit-per-second", 31402 "petabits/second": "petabit-per-second", 31403 "pb/s": "petabit-per-second", 31404 "Pb/s": "petabit-per-second", 31405 "pbps": "petabit-per-second", 31406 "Pbps": "petabit-per-second", 31407 "petabyte/s": "petabyte-per-second", 31408 "petabytes/s": "petabyte-per-second", 31409 "petabyte/second": "petabyte-per-second", 31410 "petabytes/second": "petabyte-per-second", 31411 "PB/s": "petabyte-per-second", 31412 "pB/s": "petabyte-per-second", 31413 "pBps": "petabyte-per-second", 31414 "PBps": "petabyte-per-second", 31415 "byte/h": "byte-per-hour", 31416 "bytes/h": "byte-per-hour", 31417 "byte/hour": "byte-per-hour", 31418 "bytes/hour": "byte-per-hour", 31419 "B/h": "byte-per-hour", 31420 "Bph": "byte-per-hour", 31421 "kilobyte/h": "kilobyte-per-hour", 31422 "kilobytes/h": "kilobyte-per-hour", 31423 "kilobyte/hour": "kilobyte-per-hour", 31424 "kilobytes/hour": "kilobyte-per-hour", 31425 "kB/h": "kilobyte-per-hour", 31426 "KB/h": "kilobyte-per-hour", 31427 "kBph": "kilobyte-per-hour", 31428 "KBph": "kilobyte-per-hour", 31429 "megabyte/h": "megabyte-per-hour", 31430 "megabytes/h": "megabyte-per-hour", 31431 "megabyte/hour": "megabyte-per-hour", 31432 "megabytes/hour": "megabyte-per-hour", 31433 "MB/h": "megabyte-per-hour", 31434 "MBph": "megabyte-per-hour", 31435 "gigabyte/h": "gigabyte-per-hour", 31436 "gigabytes/h": "gigabyte-per-hour", 31437 "gigabyte/hour": "gigabyte-per-hour", 31438 "gigabytes/hour": "gigabyte-per-hour", 31439 "GB/h": "gigabyte-per-hour", 31440 "GBph": "gigabyte-per-hour", 31441 "petabyte/h": "petabyte-per-hour", 31442 "petabytes/h": "petabyte-per-hour", 31443 "petabyte/hour": "petabyte-per-hour", 31444 "petabytes/hour": "petabyte-per-hour", 31445 "PB/h": "petabyte-per-hour", 31446 "PBph": "petabyte-per-hour" 31447 }; 31448 31449 (function() { 31450 DigitalSpeedUnit.aliasesLower = {}; 31451 for (var a in DigitalSpeedUnit.aliases) { 31452 DigitalSpeedUnit.aliasesLower[a.toLowerCase()] = DigitalSpeedUnit.aliases[a]; 31453 } 31454 })(); 31455 31456 /** 31457 * Convert a digitalSpeed to another measure. 31458 * @static 31459 * @param to {string} unit to convert to 31460 * @param from {string} unit to convert from 31461 * @param digitalSpeed {number} amount to be convert 31462 * @returns {number|undefined} the converted amount 31463 */ 31464 DigitalSpeedUnit.convert = function(to, from, digitalSpeed) { 31465 from = Measurement.getUnitIdCaseInsensitive(DigitalSpeedUnit, from) || from; 31466 to = Measurement.getUnitIdCaseInsensitive(DigitalSpeedUnit, to) || to; 31467 var fromRow = DigitalSpeedUnit.ratios[from]; 31468 var toRow = DigitalSpeedUnit.ratios[to]; 31469 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31470 return undefined; 31471 } 31472 var result = digitalSpeed * fromRow[toRow[0]]; 31473 return result; 31474 }; 31475 31476 /** 31477 * @private 31478 * @static 31479 */ 31480 DigitalSpeedUnit.getMeasures = function () { 31481 return Object.keys(DigitalSpeedUnit.ratios); 31482 }; 31483 31484 //register with the factory method 31485 Measurement._constructors["digitalSpeed"] = DigitalSpeedUnit; 31486 31487 31488 /*< EnergyUnit.js */ 31489 /* 31490 * EnergyUnit.js - Unit conversions for energy measurements 31491 * 31492 * Copyright © 2014-2015, 2018 JEDLSoft 31493 * 31494 * Licensed under the Apache License, Version 2.0 (the "License"); 31495 * you may not use this file except in compliance with the License. 31496 * You may obtain a copy of the License at 31497 * 31498 * http://www.apache.org/licenses/LICENSE-2.0 31499 * 31500 * Unless required by applicable law or agreed to in writing, software 31501 * distributed under the License is distributed on an "AS IS" BASIS, 31502 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31503 * 31504 * See the License for the specific language governing permissions and 31505 * limitations under the License. 31506 */ 31507 31508 /* 31509 !depends 31510 Measurement.js 31511 */ 31512 31513 31514 /** 31515 * @class 31516 * Create a new energy measurement instance. 31517 * 31518 * @constructor 31519 * @extends Measurement 31520 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31521 * the construction of this instance 31522 */ 31523 var EnergyUnit = function (options) { 31524 this.unit = "joule"; 31525 this.amount = 0; 31526 31527 this.ratios = EnergyUnit.ratios; 31528 this.aliases = EnergyUnit.aliases; 31529 this.aliasesLower = EnergyUnit.aliasesLower; 31530 this.systems = EnergyUnit.systems; 31531 31532 this.parent.call(this, options); 31533 }; 31534 31535 EnergyUnit.prototype = new Measurement(); 31536 EnergyUnit.prototype.parent = Measurement; 31537 EnergyUnit.prototype.constructor = EnergyUnit; 31538 31539 EnergyUnit.ratios = { 31540 /* index mJ J BTU kJ Wh Cal MJ kWh gJ MWh GWh */ 31541 "millijoule": [ 1, 1, 0.001, 9.4781707775e-7, 1e-6, 2.7777777778e-7, 2.3884589663e-7, 1.0e-9, 2.7777777778e-10, 1.0e-12, 2.7777777778e-13, 2.7777777778e-16 ], 31542 "joule": [ 2, 1000, 1, 9.4781707775e-4, 0.001, 2.7777777778e-4, 2.3884589663e-4, 1.0e-6, 2.7777777778e-7, 1.0e-9, 2.7777777778e-10, 2.7777777778e-13 ], 31543 "BTU": [ 3, 1055055.9, 1055.0559, 1, 1.0550559, 0.29307108333, 0.25199577243, 1.0550559e-3, 2.9307108333e-4, 1.0550559e-6, 2.9307108333e-7, 2.9307108333e-10 ], 31544 "kilojoule": [ 4, 1000000, 1000, 0.94781707775, 1, 0.27777777778, 0.23884589663, 0.001, 2.7777777778e-4, 1.0e-6, 2.7777777778e-7, 2.7777777778e-10 ], 31545 "watt-hour": [ 5, 3.6e+6, 3600, 3.4121414799, 3.6, 1, 0.85984522786, 0.0036, 0.001, 3.6e-6, 1.0e-6, 1.0e-9 ], 31546 "foodcalorie": [ 6, 4.868e+5, 4186.8, 3.9683205411, 4.1868, 1.163, 1, 4.1868e-3, 1.163e-3, 4.1868e-6, 1.163e-6, 1.163e-9 ], 31547 "megajoule": [ 7, 1e+9, 1e+6, 947.81707775, 1000, 277.77777778, 238.84589663, 1, 0.27777777778, 0.001, 2.7777777778e-4, 2.7777777778e-7 ], 31548 "kilowatt-hour":[ 8, 3.6e+9, 3.6e+6, 3412.1414799, 3600, 1000, 859.84522786, 3.6, 1, 3.6e-3, 0.001, 1e-6 ], 31549 "gigajoule": [ 9, 1e+12, 1e+9, 947817.07775, 1e+6, 277777.77778, 238845.89663, 1000, 277.77777778, 1, 0.27777777778, 2.7777777778e-4 ], 31550 "megawatt-hour":[ 10, 3.6e+12, 3.6e+9, 3412141.4799, 3.6e+6, 1e+6, 859845.22786, 3600, 1000, 3.6, 1, 0.001 ], 31551 "gigawatt-hour":[ 11, 3.6e+15, 3.6e+12, 3412141479.9, 3.6e+9, 1e+9, 859845227.86, 3.6e+6, 1e+6, 3600, 1000, 1 ] 31552 }; 31553 31554 /** 31555 * Return the type of this measurement. Examples are "mass", 31556 * "length", "speed", etc. Measurements can only be converted 31557 * to measurements of the same type.<p> 31558 * 31559 * The type of the units is determined automatically from the 31560 * units. For example, the unit "grams" is type "mass". Use the 31561 * static call {@link Measurement.getAvailableUnits} 31562 * to find out what units this version of ilib supports. 31563 * 31564 * @return {string} the name of the type of this measurement 31565 */ 31566 EnergyUnit.prototype.getMeasure = function() { 31567 return "energy"; 31568 }; 31569 31570 /** 31571 * Return a new instance of this type of measurement. 31572 * 31573 * @param {Object} params parameters to the constructor 31574 * @return {Measurement} a measurement subclass instance 31575 */ 31576 EnergyUnit.prototype.newUnit = function(params) { 31577 return new EnergyUnit(params); 31578 }; 31579 31580 EnergyUnit.aliases = { 31581 "milli joule": "millijoule", 31582 "millijoule": "millijoule", 31583 "milliJ": "millijoule", 31584 "mJ": "millijoule", 31585 "joule": "joule", 31586 "joules": "joule", 31587 "J": "joule", 31588 "BTU": "BTU", 31589 "British Thermal Unit": "BTU", 31590 "British Thermal Units": "BTU", 31591 "kilo joule": "kilojoule", 31592 "kilojoule": "kilojoule", 31593 "kilojoules": "kilojoule", 31594 "kjoule": "kilojoule", 31595 "kJ": "kilojoule", 31596 "watt hour": "watt-hour", 31597 "watt hours": "watt-hour", 31598 "Wh": "watt-hour", 31599 "food calorie": "foodcalorie", 31600 "food calories": "foodcalorie", 31601 "calorie": "foodcalorie", 31602 "calories": "foodcalorie", 31603 "Cal": "foodcalorie", 31604 "mega joule": "megajoule", 31605 "mega joules": "megajoule", 31606 "megajoule": "megajoule", 31607 "megajoules": "megajoule", 31608 "MJ": "megajoule", 31609 "kilo watt hour": "kilowatt-hour", 31610 "kilo watt hours": "kilowatt-hour", 31611 "kiloWh": "kilowatt-hour", 31612 "kilowatt hour": "kilowatt-hour", 31613 "kilowatt hours": "kilowatt-hour", 31614 "kilowatt-hour": "kilowatt-hour", 31615 "kilowatt-hours": "kilowatt-hour", 31616 "kilowatthour": "kilowatt-hour", 31617 "kilowatthours": "kilowatt-hour", 31618 "kW hour": "kilowatt-hour", 31619 "kW hours": "kilowatt-hour", 31620 "kW-hour": "kilowatt-hour", 31621 "kW-hours": "kilowatt-hour", 31622 "kWh": "kilowatt-hour", 31623 "giga joule": "gigajoule", 31624 "Gj": "gigajoule", 31625 "gigajoule": "gigajoule", 31626 "gigajoules": "gigajoule", 31627 "mega watt hour": "megawatt-hour", 31628 "mega watt hours": "megawatt-hour", 31629 "megawatt hour": "megawatt-hour", 31630 "megawatt hours": "megawatt-hour", 31631 "megawatt-hour": "megawatt-hour", 31632 "megawatt-hours": "megawatt-hour", 31633 "MW hour": "megawatt-hour", 31634 "MW hours": "megawatt-hour", 31635 "MW-hour": "megawatt-hour", 31636 "MW-hours": "megawatt-hour", 31637 "megaWh": "megawatt-hour", 31638 "MWh": "megawatt-hour", 31639 "giga watt hour": "gigawatt-hour", 31640 "giga watt hours": "gigawatt-hour", 31641 "gigawatt hour": "gigawatt-hour", 31642 "gigawatt hours": "gigawatt-hour", 31643 "gigawatt-hours": "gigawatt-hour", 31644 "gigawatthour": "gigawatt-hour", 31645 "GW hour": "gigawatt-hour", 31646 "GW hours": "gigawatt-hour", 31647 "GW-hour": "gigawatt-hour", 31648 "GW-hours": "gigawatt-hour", 31649 "gigaWh": "gigawatt-hour", 31650 "GWh": "gigawatt-hour" 31651 }; 31652 31653 (function() { 31654 EnergyUnit.aliasesLower = {}; 31655 for (var a in EnergyUnit.aliases) { 31656 EnergyUnit.aliasesLower[a.toLowerCase()] = EnergyUnit.aliases[a]; 31657 } 31658 })(); 31659 31660 /** 31661 * Convert a energy to another measure. 31662 * @static 31663 * @param to {string} unit to convert to 31664 * @param from {string} unit to convert from 31665 * @param energy {number} amount to be convert 31666 * @returns {number|undefined} the converted amount 31667 */ 31668 EnergyUnit.convert = function(to, from, energy) { 31669 from = Measurement.getUnitIdCaseInsensitive(EnergyUnit, from) || from; 31670 to = Measurement.getUnitIdCaseInsensitive(EnergyUnit, to) || to; 31671 var fromRow = EnergyUnit.ratios[from]; 31672 var toRow = EnergyUnit.ratios[to]; 31673 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31674 return undefined; 31675 } 31676 return energy * fromRow[toRow[0]]; 31677 }; 31678 31679 EnergyUnit.systems = { 31680 "metric": [ 31681 "millijoule", 31682 "joule", 31683 "kilojoule", 31684 "watt-hour", 31685 "megajoule", 31686 "kilowatt-hour", 31687 "gigajoule", 31688 "megawatt-hour", 31689 "gigawatt-hour" 31690 ], 31691 "imperial": [ 31692 "BTU", 31693 "foodcalorie" 31694 ], 31695 "uscustomary": [ 31696 "BTU", 31697 "foodcalorie" 31698 ], 31699 "conversions": { 31700 "metric": { 31701 "uscustomary": { 31702 "millijoule": "BTU", 31703 "joule": "BTU", 31704 "kilojoule": "BTU", 31705 "watt-hour": "BTU", 31706 "megajoule": "BTU", 31707 "kilowatt-hour": "BTU", 31708 "gigajoule": "BTU", 31709 "megawatt-hour": "BTU", 31710 "gigawatt-hour": "BTU" 31711 }, 31712 "imperial": { 31713 "millijoule": "BTU", 31714 "joule": "BTU", 31715 "kilojoule": "BTU", 31716 "watt-hour": "BTU", 31717 "megajoule": "BTU", 31718 "kilowatt-hour": "BTU", 31719 "gigajoule": "BTU", 31720 "megawatt-hour": "BTU", 31721 "gigawatt-hour": "BTU" 31722 } 31723 }, 31724 "uscustomary": { 31725 "metric": { 31726 "BTU": "joule", 31727 "foodcalorie": "joule" 31728 } 31729 }, 31730 "imperial": { 31731 "metric": { 31732 "BTU": "joule", 31733 "foodcalorie": "joule" 31734 } 31735 } 31736 } 31737 }; 31738 31739 /** 31740 * @private 31741 * @static 31742 */ 31743 EnergyUnit.getMeasures = function () { 31744 return Object.keys(EnergyUnit.ratios); 31745 }; 31746 31747 //register with the factory method 31748 Measurement._constructors["energy"] = EnergyUnit; 31749 31750 31751 /*< FuelConsumptionUnit.js */ 31752 /* 31753 * FuelConsumptionUnit.js - Unit conversions for fuel consumption measurements 31754 * 31755 * Copyright © 2014-2015, 2018 JEDLSoft 31756 * 31757 * Licensed under the Apache License, Version 2.0 (the "License"); 31758 * you may not use this file except in compliance with the License. 31759 * You may obtain a copy of the License at 31760 * 31761 * http://www.apache.org/licenses/LICENSE-2.0 31762 * 31763 * Unless required by applicable law or agreed to in writing, software 31764 * distributed under the License is distributed on an "AS IS" BASIS, 31765 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 31766 * 31767 * See the License for the specific language governing permissions and 31768 * limitations under the License. 31769 */ 31770 31771 /* 31772 !depends 31773 Measurement.js 31774 */ 31775 31776 31777 /** 31778 * @class 31779 * Create a new fuelconsumption measurement instance. 31780 * 31781 * @constructor 31782 * @extends Measurement 31783 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 31784 * the construction of this instance 31785 */ 31786 var FuelConsumptionUnit = function(options) { 31787 this.unit = "liter-per-100kilometers"; 31788 this.amount = 0; 31789 31790 this.ratios = FuelConsumptionUnit.ratios; 31791 this.aliases = FuelConsumptionUnit.aliases; 31792 this.aliasesLower = FuelConsumptionUnit.aliasesLower; 31793 this.systems = FuelConsumptionUnit.systems; 31794 31795 this.parent.call(this, options); 31796 }; 31797 31798 FuelConsumptionUnit.prototype = new Measurement(); 31799 FuelConsumptionUnit.prototype.parent = Measurement; 31800 FuelConsumptionUnit.prototype.constructor = FuelConsumptionUnit; 31801 31802 FuelConsumptionUnit.ratios = { 31803 /* index km/L L/km L/100km mpg mpgi inverse? */ 31804 "kilometer-per-liter": [ 1, 1, 1, 100, 2.35215, 2.82481, false ], 31805 "liter-per-kilometer": [ 2, 1, 1, 0.01, 2.35215, 2.82481, true ], 31806 "liter-per-100kilometers": [ 3, 100, 0.01, 1, 235.215, 282.481, true ], 31807 "mile-per-gallon": [ 4, 0.425144, 2.35215, 235.215, 1, 1.20095, false ], 31808 "mile-per-gallon-imperial": [ 5, 0.354006, 2.82481, 282.481, 0.8326741, 1, false ] 31809 }; 31810 31811 /** 31812 * Return the type of this measurement. Examples are "mass", 31813 * "length", "speed", etc. Measurements can only be converted 31814 * to measurements of the same type.<p> 31815 * 31816 * The type of the units is determined automatically from the 31817 * units. For example, the unit "grams" is type "mass". Use the 31818 * static call {@link Measurement.getAvailableUnits} 31819 * to find out what units this version of ilib supports. 31820 * 31821 * @return {string} the name of the type of this measurement 31822 */ 31823 FuelConsumptionUnit.prototype.getMeasure = function() { 31824 return "fuelconsumption"; 31825 }; 31826 31827 /** 31828 * Return a new instance of this type of measurement. 31829 * 31830 * @param {Object} params parameters to the constructor 31831 * @return {Measurement} a measurement subclass instance 31832 */ 31833 FuelConsumptionUnit.prototype.newUnit = function(params) { 31834 return new FuelConsumptionUnit(params); 31835 }; 31836 31837 FuelConsumptionUnit.aliases = { 31838 "Km/liter": "kilometer-per-liter", 31839 "KM/Liter": "kilometer-per-liter", 31840 "KM/L": "kilometer-per-liter", 31841 "Kilometers Per Liter": "kilometer-per-liter", 31842 "kilometers per liter": "kilometer-per-liter", 31843 "km/l": "kilometer-per-liter", 31844 "Kilometers/Liter": "kilometer-per-liter", 31845 "Kilometer/Liter": "kilometer-per-liter", 31846 "kilometers/liter": "kilometer-per-liter", 31847 "kilometer/liter": "kilometer-per-liter", 31848 "km/liter": "kilometer-per-liter", 31849 "Liter/100km": "liter-per-100kilometers", 31850 "Liters/100km": "liter-per-100kilometers", 31851 "Liter/100kms": "liter-per-100kilometers", 31852 "Liters/100kms": "liter-per-100kilometers", 31853 "liter/100km": "liter-per-100kilometers", 31854 "liters/100kms": "liter-per-100kilometers", 31855 "liters/100km": "liter-per-100kilometers", 31856 "liter/100kms": "liter-per-100kilometers", 31857 "Liter/100KM": "liter-per-100kilometers", 31858 "Liters/100KM": "liter-per-100kilometers", 31859 "L/100km": "liter-per-100kilometers", 31860 "L/100KM": "liter-per-100kilometers", 31861 "l/100KM": "liter-per-100kilometers", 31862 "l/100km": "liter-per-100kilometers", 31863 "l/100kms": "liter-per-100kilometers", 31864 "Liter/km": "liter-per-kilometer", 31865 "Liters/km": "liter-per-kilometer", 31866 "Liter/kms": "liter-per-kilometer", 31867 "Liters/kms": "liter-per-kilometer", 31868 "liter/km": "liter-per-kilometer", 31869 "liters/kms": "liter-per-kilometer", 31870 "liters/km": "liter-per-kilometer", 31871 "liter/kms": "liter-per-kilometer", 31872 "Liter/KM": "liter-per-kilometer", 31873 "Liters/KM": "liter-per-kilometer", 31874 "L/km": "liter-per-kilometer", 31875 "L/KM": "liter-per-kilometer", 31876 "l/KM": "liter-per-kilometer", 31877 "l/km": "liter-per-kilometer", 31878 "l/kms": "liter-per-kilometer", 31879 "MPG(US)": "mile-per-gallon", 31880 "USMPG ": "mile-per-gallon", 31881 "mpg": "mile-per-gallon", 31882 "mpgUS": "mile-per-gallon", 31883 "mpg(US)": "mile-per-gallon", 31884 "mpg(us)": "mile-per-gallon", 31885 "mpg-us": "mile-per-gallon", 31886 "mpg Imp": "mile-per-gallon-imperial", 31887 "MPG(imp)": "mile-per-gallon-imperial", 31888 "mpg(imp)": "mile-per-gallon-imperial", 31889 "mpg-imp": "mile-per-gallon-imperial" 31890 }; 31891 31892 (function() { 31893 FuelConsumptionUnit.aliasesLower = {}; 31894 for (var a in FuelConsumptionUnit.aliases) { 31895 FuelConsumptionUnit.aliasesLower[a.toLowerCase()] = FuelConsumptionUnit.aliases[a]; 31896 } 31897 })(); 31898 31899 /** 31900 * Return a new measurement instance that is converted to a new 31901 * measurement unit. Measurements can only be converted 31902 * to measurements of the same type.<p> 31903 * 31904 * @param {string} to The name of the units to convert to 31905 * @return {number|undefined} the converted measurement 31906 * or undefined if the requested units are for a different 31907 * measurement type 31908 */ 31909 FuelConsumptionUnit.prototype.convert = function(to) { 31910 if (!to || typeof(FuelConsumptionUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 31911 return undefined; 31912 } 31913 return FuelConsumptionUnit.convert(to, this.unit, this.amount); 31914 }; 31915 31916 FuelConsumptionUnit.systems = { 31917 "metric": [ 31918 "liter-per-kilometer", 31919 "liter-per-100kilometers", 31920 "kilometer-per-liter" 31921 ], 31922 "uscustomary": [ 31923 "mile-per-gallon" 31924 ], 31925 "imperial": [ 31926 "mile-per-gallon-imperial" 31927 ], 31928 "conversions": { 31929 "metric": { 31930 "uscustomary": { 31931 "liter-per-kilometer": "mile-per-gallon", 31932 "kilometer-per-liter": "mile-per-gallon", 31933 "liter-per-100kilometers": "mile-per-gallon" 31934 }, 31935 "imperial": { 31936 "liter-per-kilometer": "mile-per-gallon-imperial", 31937 "kilometer-per-liter": "mile-per-gallon-imperial", 31938 "liter-per-100kilometers": "mile-per-gallon-imperial" 31939 } 31940 }, 31941 "uscustomary": { 31942 "metric": { 31943 "mile-per-gallon": "liter-per-100kilometers" 31944 }, 31945 "imperial": { 31946 "mile-per-gallon": "mile-per-gallon-imperial" 31947 } 31948 }, 31949 "imperial": { 31950 "metric": { 31951 "mile-per-gallon-imperial": "liter-per-100kilometers" 31952 }, 31953 "uscustomary": { 31954 "mile-per-gallon-imperial": "mile-per-gallon" 31955 } 31956 } 31957 } 31958 }; 31959 31960 /** 31961 * Convert a FuelConsumption to another measure. 31962 * 31963 * @static 31964 * @param to {string} unit to convert to 31965 * @param from {string} unit to convert from 31966 * @param fuelConsumption {number} amount to be convert 31967 * @returns {number|undefined} the converted amount 31968 */ 31969 FuelConsumptionUnit.convert = function(to, from, fuelConsumption) { 31970 from = Measurement.getUnitIdCaseInsensitive(FuelConsumptionUnit, from) || from; 31971 to = Measurement.getUnitIdCaseInsensitive(FuelConsumptionUnit, to) || to; 31972 var fromRow = FuelConsumptionUnit.ratios[from]; 31973 var toRow = FuelConsumptionUnit.ratios[to]; 31974 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 31975 return undefined; 31976 } 31977 31978 if (fromRow[6] !== toRow[6]) { 31979 // inverses of each other. Avoid the divide by 0. 31980 return fuelConsumption ? (fromRow[toRow[0]] / fuelConsumption) : 0; 31981 } 31982 31983 // not inverses, so just multiply by the factor 31984 return fuelConsumption * fromRow[toRow[0]]; 31985 }; 31986 31987 /** 31988 * Scale the measurement unit to an acceptable level. The scaling 31989 * happens so that the integer part of the amount is as small as 31990 * possible without being below zero. This will result in the 31991 * largest units that can represent this measurement without 31992 * fractions. Measurements can only be scaled to other measurements 31993 * of the same type. 31994 * 31995 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 31996 * or undefined if the system can be inferred from the current measure 31997 * @param {Object=} units mapping from the measurement system to the units to use 31998 * for this scaling. If this is not defined, this measurement type will use the 31999 * set of units that it knows about for the given measurement system 32000 * @return {Measurement} a new instance that is scaled to the 32001 * right level 32002 */ 32003 FuelConsumptionUnit.prototype.scale = function(measurementsystem, units) { 32004 return new FuelConsumptionUnit({ 32005 unit: this.unit, 32006 amount: this.amount 32007 }); 32008 }; 32009 32010 /** 32011 * Expand the current measurement such that any fractions of the current unit 32012 * are represented in terms of smaller units in the same system instead of fractions 32013 * of the current unit. For example, "6.25 feet" may be represented as 32014 * "6 feet 4 inches" instead. The return value is an array of measurements which 32015 * are progressively smaller until the smallest unit in the system is reached 32016 * or until there is a whole number of any unit along the way. 32017 * 32018 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 32019 * or undefined if the system can be inferred from the current measure 32020 * @param {Object=} units mapping from the measurement system to the units to use 32021 * for this scaling. If this is not defined, this measurement type will use the 32022 * set of units that it knows about for the given measurement system 32023 * @return {Array.<Measurement>} an array of new measurements in order from 32024 * the current units to the smallest units in the system which together are the 32025 * same measurement as this one 32026 */ 32027 FuelConsumptionUnit.prototype.expand = function(measurementsystem, units) { 32028 return [this]; // nothing to expand 32029 }; 32030 32031 /** 32032 * @private 32033 * @static 32034 */ 32035 FuelConsumptionUnit.getMeasures = function() { 32036 return Object.keys(FuelConsumptionUnit.ratios); 32037 }; 32038 32039 //register with the factory method 32040 Measurement._constructors["fuelconsumption"] = FuelConsumptionUnit; 32041 32042 32043 /*< LengthUnit.js */ 32044 /* 32045 * LengthUnit.js - Unit conversions for length measurements 32046 * 32047 * Copyright © 2014-2015, 2018 JEDLSoft 32048 * 32049 * Licensed under the Apache License, Version 2.0 (the "License"); 32050 * you may not use this file except in compliance with the License. 32051 * You may obtain a copy of the License at 32052 * 32053 * http://www.apache.org/licenses/LICENSE-2.0 32054 * 32055 * Unless required by applicable law or agreed to in writing, software 32056 * distributed under the License is distributed on an "AS IS" BASIS, 32057 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32058 * 32059 * See the License for the specific language governing permissions and 32060 * limitations under the License. 32061 */ 32062 32063 /* 32064 !depends 32065 Measurement.js 32066 */ 32067 32068 32069 /** 32070 * @class 32071 * Create a new length measurement instance. 32072 * 32073 * @constructor 32074 * @extends Measurement 32075 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 32076 * the construction of this instance 32077 */ 32078 var LengthUnit = function (options) { 32079 this.unit = "meter"; 32080 this.amount = 0; 32081 32082 this.ratios = LengthUnit.ratios; 32083 this.aliases = LengthUnit.aliases; 32084 this.aliasesLower = LengthUnit.aliasesLower; 32085 this.systems = LengthUnit.systems; 32086 32087 this.parent.call(this, options); 32088 }; 32089 32090 LengthUnit.prototype = new Measurement(); 32091 LengthUnit.prototype.parent = Measurement; 32092 LengthUnit.prototype.constructor = LengthUnit; 32093 32094 LengthUnit.ratios = { 32095 /* index, µm mm cm inch dm foot yard m dam hm km mile nm Mm Gm */ 32096 "micrometer": [ 1, 1, 1e-3, 1e-4, 3.93701e-5, 1e-5, 3.28084e-6, 1.09361e-6, 1e-6, 1e-7, 1e-8, 1e-9, 6.21373e-10, 5.39957e-10, 1e-12, 1e-15 ], 32097 "millimeter": [ 2, 1000, 1, 0.1, 0.0393701, 0.01, 0.00328084, 1.09361e-3, 0.001, 1e-4, 1e-5, 1e-6, 6.21373e-7, 5.39957e-7, 1e-9, 1e-12 ], 32098 "centimeter": [ 3, 1e4, 10, 1, 0.393701, 0.1, 0.0328084, 0.0109361, 0.01, 0.001, 1e-4, 1e-5, 6.21373e-6, 5.39957e-6, 1e-8, 1e-9 ], 32099 "inch": [ 4, 25399.986, 25.399986, 2.5399986, 1, 0.25399986, 0.083333333, 0.027777778, 0.025399986, 2.5399986e-3, 2.5399986e-4, 2.5399986e-5, 1.5783e-5, 1.3715e-5, 2.5399986e-8, 2.5399986e-11 ], 32100 "decimeter": [ 5, 1e5, 100, 10, 3.93701, 1, 0.328084, 0.109361, 0.1, 0.01, 0.001, 1e-4, 6.21373e-5, 5.39957e-5, 1e-7, 1e-8 ], 32101 "foot": [ 6, 304799.99, 304.79999, 30.479999, 12, 3.0479999, 1, 0.33333333, 0.30479999, 0.030479999, 3.0479999e-3, 3.0479999e-4, 1.89394e-4, 1.64579e-4, 3.0479999e-7, 3.0479999e-10 ], 32102 "yard": [ 7, 914402.758, 914.402758, 91.4402758, 36, 9.14402758, 3, 1, 0.914402758, 0.0914402758, 9.14402758e-3, 9.14402758e-4, 5.68182e-4, 4.93737e-4, 9.14402758e-7, 9.14402758e-10 ], 32103 "meter": [ 8, 1e6, 1000, 100, 39.3701, 10, 3.28084, 1.09361, 1, 0.1, 0.01, 0.001, 6.213712e-4, 5.39957e-4, 1e-6, 1e-7 ], 32104 "decameter": [ 9, 1e7, 1e4, 1000, 393.701, 100, 32.8084, 10.9361, 10, 1, 0.1, 0.01, 6.21373e-3, 5.39957e-3, 1e-5, 1e-6 ], 32105 "hectometer": [ 10, 1e8, 1e5, 1e4, 3937.01, 1000, 328.084, 109.361, 100, 10, 1, 0.1, 0.0621373, 0.0539957, 1e-4, 1e-5 ], 32106 "kilometer": [ 11, 1e9, 1e6, 1e5, 39370.1, 1e4, 3280.84, 1093.61, 1000, 100, 10, 1, 0.621373, 0.539957, 0.001, 1e-4 ], 32107 "mile": [ 12, 1.60934e9, 1.60934e6, 1.60934e5, 63360, 1.60934e4, 5280, 1760, 1609.34, 160.934, 16.0934, 1.60934, 1, 0.868976, 1.60934e-3, 1.60934e-6 ], 32108 "nautical-mile": [ 13, 1.852e9, 1.852e6, 1.852e5, 72913.4, 1.852e4, 6076.12, 2025.37, 1852, 185.2, 18.52, 1.852, 1.15078, 1, 1.852e-3, 1.852e-6 ], 32109 "megameter": [ 14, 1e12, 1e9, 1e6, 3.93701e7, 1e5, 3.28084e6, 1.09361e6, 1e4, 1000, 100, 10, 621.373, 539.957, 1, 0.001 ], 32110 "gigameter": [ 15, 1e15, 1e12, 1e9, 3.93701e10, 1e8, 3.28084e9, 1.09361e9, 1e7, 1e6, 1e5, 1e4, 621373.0, 539957.0, 1000, 1 ] 32111 }; 32112 32113 /** 32114 * Return a new instance of this type of measurement. 32115 * 32116 * @param {Object} params parameters to the constructor 32117 * @return {Measurement} a measurement subclass instance 32118 */ 32119 LengthUnit.prototype.newUnit = function(params) { 32120 return new LengthUnit(params); 32121 }; 32122 32123 LengthUnit.systems = { 32124 "metric": [ 32125 "micrometer", 32126 "millimeter", 32127 "centimeter", 32128 "decimeter", 32129 "meter", 32130 "decameter", 32131 "hectometer", 32132 "kilometer", 32133 "megameter", 32134 "gigameter" 32135 ], 32136 "imperial": [ 32137 "inch", 32138 "foot", 32139 "yard", 32140 "mile", 32141 "nautical-mile" 32142 ], 32143 "uscustomary": [ 32144 "inch", 32145 "foot", 32146 "yard", 32147 "mile", 32148 "nautical-mile" 32149 ], 32150 "conversions": { 32151 "metric": { 32152 "uscustomary": { 32153 "micrometer": "inch", 32154 "millimeter": "inch", 32155 "centimeter": "inch", 32156 "decimeter": "inch", 32157 "meter": "yard", 32158 "decameter": "yard", 32159 "hectometer": "mile", 32160 "kilometer": "mile", 32161 "megameter": "mile", 32162 "gigameter": "mile" 32163 }, 32164 "imperial": { 32165 "micrometer": "inch", 32166 "millimeter": "inch", 32167 "centimeter": "inch", 32168 "decimeter": "inch", 32169 "meter": "yard", 32170 "decameter": "yard", 32171 "hectometer": "mile", 32172 "kilometer": "mile", 32173 "megameter": "mile", 32174 "gigameter": "mile" 32175 } 32176 }, 32177 "uscustomary": { 32178 "metric": { 32179 "inch": "centimeter", 32180 "foot": "centimeter", 32181 "yard": "meter", 32182 "mile": "kilometer", 32183 "nautical-mile": "kilometer" 32184 } 32185 }, 32186 "imperial": { 32187 "metric": { 32188 "inch": "centimeter", 32189 "foot": "centimeter", 32190 "yard": "meter", 32191 "mile": "kilometer", 32192 "nautical-mile": "kilometer" 32193 } 32194 } 32195 } 32196 }; 32197 32198 /** 32199 * Return the type of this measurement. Examples are "mass", 32200 * "length", "speed", etc. Measurements can only be converted 32201 * to measurements of the same type.<p> 32202 * 32203 * The type of the units is determined automatically from the 32204 * units. For example, the unit "grams" is type "mass". Use the 32205 * static call {@link Measurement.getAvailableUnits} 32206 * to find out what units this version of ilib supports. 32207 * 32208 * @returns {string} the name of the type of this measurement 32209 */ 32210 LengthUnit.prototype.getMeasure = function() { 32211 return "length"; 32212 }; 32213 32214 LengthUnit.aliases = { 32215 "miles": "mile", 32216 "mile":"mile", 32217 "nauticalmiles": "nautical-mile", 32218 "nautical mile": "nautical-mile", 32219 "nautical miles": "nautical-mile", 32220 "nauticalmile":"nautical-mile", 32221 "yards": "yard", 32222 "yard": "yard", 32223 "feet": "foot", 32224 "foot": "foot", 32225 "inches": "inch", 32226 "inch": "inch", 32227 "in": "inch", 32228 "meters": "meter", 32229 "metre": "meter", 32230 "metres": "meter", 32231 "m": "meter", 32232 "meter": "meter", 32233 "micrometers": "micrometer", 32234 "micrometres": "micrometer", 32235 "micrometre": "micrometer", 32236 "µm": "micrometer", 32237 "micrometer": "micrometer", 32238 "millimeters": "millimeter", 32239 "millimetres": "millimeter", 32240 "millimetre": "millimeter", 32241 "mm": "millimeter", 32242 "millimeter": "millimeter", 32243 "centimeters": "centimeter", 32244 "centimetres": "centimeter", 32245 "centimetre": "centimeter", 32246 "cm": "centimeter", 32247 "centimeter": "centimeter", 32248 "decimeters": "decimeter", 32249 "decimetres": "decimeter", 32250 "decimetre": "decimeter", 32251 "dm": "decimeter", 32252 "decimeter": "decimeter", 32253 "decameters": "decameter", 32254 "decametres": "decameter", 32255 "decametre": "decameter", 32256 "dam": "decameter", 32257 "decameter": "decameter", 32258 "hectometers": "hectometer", 32259 "hectometres": "hectometer", 32260 "hectometre": "hectometer", 32261 "hm": "hectometer", 32262 "hectometer": "hectometer", 32263 "kilometers": "kilometer", 32264 "kilometres": "kilometer", 32265 "kilometre": "kilometer", 32266 "km": "kilometer", 32267 "kilometer": "kilometer", 32268 "megameters": "megameter", 32269 "megametres": "megameter", 32270 "megametre": "megameter", 32271 "Mm": "megameter", 32272 "megameter": "megameter", 32273 "gigameters": "gigameter", 32274 "gigametres": "gigameter", 32275 "gigametre": "gigameter", 32276 "Gm": "gigameter", 32277 "gigameter": "gigameter" 32278 }; 32279 32280 (function() { 32281 LengthUnit.aliasesLower = {}; 32282 for (var a in LengthUnit.aliases) { 32283 LengthUnit.aliasesLower[a.toLowerCase()] = LengthUnit.aliases[a]; 32284 } 32285 })(); 32286 32287 /** 32288 * Convert a length to another measure. 32289 * @static 32290 * @param to {string} unit to convert to 32291 * @param from {string} unit to convert from 32292 * @param length {number} amount to be convert 32293 * @returns {number|undefined} the converted amount 32294 */ 32295 LengthUnit.convert = function(to, from, length) { 32296 from = Measurement.getUnitIdCaseInsensitive(LengthUnit, from) || from; 32297 to = Measurement.getUnitIdCaseInsensitive(LengthUnit, to) || to; 32298 var fromRow = LengthUnit.ratios[from]; 32299 var toRow = LengthUnit.ratios[to]; 32300 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 32301 return undefined; 32302 } 32303 return length * fromRow[toRow[0]]; 32304 }; 32305 32306 /** 32307 * @private 32308 * @static 32309 */ 32310 LengthUnit.getMeasures = function () { 32311 return Object.keys(LengthUnit.ratios); 32312 }; 32313 32314 //register with the factory method 32315 Measurement._constructors["length"] = LengthUnit; 32316 32317 32318 /*< MassUnit.js */ 32319 /* 32320 * MassUnit.js - Unit conversions for weight/mass measurements 32321 * 32322 * Copyright © 2014-2015, 2018 JEDLSoft 32323 * 32324 * Licensed under the Apache License, Version 2.0 (the "License"); 32325 * you may not use this file except in compliance with the License. 32326 * You may obtain a copy of the License at 32327 * 32328 * http://www.apache.org/licenses/LICENSE-2.0 32329 * 32330 * Unless required by applicable law or agreed to in writing, software 32331 * distributed under the License is distributed on an "AS IS" BASIS, 32332 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32333 * 32334 * See the License for the specific language governing permissions and 32335 * limitations under the License. 32336 */ 32337 32338 /* 32339 !depends 32340 Measurement.js 32341 */ 32342 32343 32344 /** 32345 * @class 32346 * Create a new mass measurement instance. 32347 * 32348 * @constructor 32349 * @extends Measurement 32350 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 32351 * the construction of this instance 32352 */ 32353 var MassUnit = function (options) { 32354 this.unit = "gram"; 32355 this.amount = 0; 32356 32357 this.ratios = MassUnit.ratios; 32358 this.aliases = MassUnit.aliases; 32359 this.aliasesLower = MassUnit.aliasesLower; 32360 this.systems = MassUnit.systems; 32361 32362 this.parent.call(this, options); 32363 }; 32364 32365 MassUnit.prototype = new Measurement(); 32366 MassUnit.prototype.parent = Measurement; 32367 MassUnit.prototype.constructor = MassUnit; 32368 32369 MassUnit.ratios = { 32370 /* index µg mg g oz lb kg st sh ton mt ton ln ton */ 32371 "microgram": [ 1, 1, 0.001, 1e-6, 3.5274e-8, 2.2046e-9, 1e-9, 1.5747e-10, 1.1023e-12, 1e-12, 9.8421e-13 ], 32372 "milligram": [ 2, 1000, 1, 0.001, 3.5274e-5, 2.2046e-6, 1e-6, 1.5747e-7, 1.1023e-9, 1e-9, 9.8421e-10 ], 32373 "gram": [ 3, 1e+6, 1000, 1, 0.035274, 0.00220462, 0.001, 0.000157473, 1.1023e-6, 1e-6, 9.8421e-7 ], 32374 "ounce": [ 4, 2.835e+7, 28349.5, 28.3495, 1, 0.0625, 0.0283495, 0.00446429, 3.125e-5, 2.835e-5, 2.7902e-5 ], 32375 "pound": [ 5, 4.536e+8, 453592, 453.592, 16, 1, 0.453592, 0.0714286, 0.0005, 0.000453592, 0.000446429 ], 32376 "kilogram": [ 6, 1e+9, 1e+6, 1000, 35.274, 2.20462, 1, 0.157473, 0.00110231, 0.001, 0.000984207 ], 32377 "stone": [ 7, 6.35e+9, 6.35e+6, 6350.29, 224, 14, 6.35029, 1, 0.007, 0.00635029, 0.00625 ], 32378 "short-ton": [ 8, 9.072e+11, 9.072e+8, 907185, 32000, 2000, 907.185, 142.857, 1, 0.907185, 0.892857 ], 32379 "metric-ton": [ 9, 1e+12, 1e+9, 1e+6, 35274, 2204.62, 1000, 157.473, 1.10231, 1, 0.984207 ], 32380 "long-ton": [ 10, 1.016e+12, 1.016e+9, 1.016e+6, 35840, 2240, 1016.05, 160, 1.12, 1.01605, 1 ] 32381 }; 32382 32383 /** 32384 * Return a new instance of this type of measurement. 32385 * 32386 * @param {Object} params parameters to the constructor 32387 * @return {Measurement} a measurement subclass instance 32388 */ 32389 MassUnit.prototype.newUnit = function(params) { 32390 return new MassUnit(params); 32391 }; 32392 32393 MassUnit.systems = { 32394 "metric": [ 32395 "microgram", 32396 "milligram", 32397 "gram", 32398 "kilogram", 32399 "metric-ton" 32400 ], 32401 "imperial": [ 32402 "ounce", 32403 "pound", 32404 "stone", 32405 "long-ton" 32406 ], 32407 "uscustomary": [ 32408 "ounce", 32409 "pound", 32410 "short-ton" 32411 ], 32412 "conversions": { 32413 "metric": { 32414 "uscustomary": { 32415 "microgram": "ounce", 32416 "milligram": "ounce", 32417 "gram": "ounce", 32418 "kilogram": "pound", 32419 "metric-ton": "short-ton" 32420 }, 32421 "imperial": { 32422 "microgram": "ounce", 32423 "milligram": "ounce", 32424 "gram": "ounce", 32425 "kilogram": "pound", 32426 "metric-ton": "long-ton" 32427 } 32428 }, 32429 "uscustomary": { 32430 "imperial": { 32431 "ounce": "ounce", 32432 "pound": "pound", 32433 "short-ton": "long-ton" 32434 }, 32435 "metric": { 32436 "ounce": "gram", 32437 "pound": "kilogram", 32438 "short-ton": "metric-ton" 32439 } 32440 }, 32441 "imperial": { 32442 "uscustomary": { 32443 "ounce": "ounce", 32444 "pound": "pound", 32445 "stone": "pound", 32446 "long-ton": "short-ton" 32447 }, 32448 "metric": { 32449 "ounce": "gram", 32450 "pound": "kilogram", 32451 "stone": "kilogram", 32452 "long-ton": "metric-ton" 32453 } 32454 } 32455 } 32456 }; 32457 32458 /** 32459 * Return the type of this measurement. Examples are "mass", 32460 * "length", "speed", etc. Measurements can only be converted 32461 * to measurements of the same type.<p> 32462 * 32463 * The type of the units is determined automatically from the 32464 * units. For example, the unit "grams" is type "mass". Use the 32465 * static call {@link Measurement.getAvailableUnits} 32466 * to find out what units this version of ilib supports. 32467 * 32468 * @return {string} the name of the type of this measurement 32469 */ 32470 MassUnit.prototype.getMeasure = function() { 32471 return "mass"; 32472 }; 32473 32474 MassUnit.aliases = { 32475 "µg":"microgram", 32476 "microgram":"microgram", 32477 "mcg":"microgram", 32478 "milligram":"milligram", 32479 "mg":"milligram", 32480 "milligrams":"milligram", 32481 "Milligram":"milligram", 32482 "Milligrams":"milligram", 32483 "MilliGram":"milligram", 32484 "MilliGrams":"milligram", 32485 "g":"gram", 32486 "gram":"gram", 32487 "grams":"gram", 32488 "Gram":"gram", 32489 "Grams":"gram", 32490 "ounce":"ounce", 32491 "oz":"ounce", 32492 "Ounce":"ounce", 32493 "ounces":"ounce", 32494 "Ounces":"ounce", 32495 "℥":"ounce", 32496 "pound":"pound", 32497 "poundm":"pound", 32498 "℔":"pound", 32499 "lb":"pound", 32500 "lbs":"pound", 32501 "pounds":"pound", 32502 "Pound":"pound", 32503 "Pounds":"pound", 32504 "kilogram":"kilogram", 32505 "kg":"kilogram", 32506 "kilograms":"kilogram", 32507 "kilo grams":"kilogram", 32508 "kilo gram":"kilogram", 32509 "Kilogram":"kilogram", 32510 "Kilograms":"kilogram", 32511 "KiloGram":"kilogram", 32512 "KiloGrams":"kilogram", 32513 "Kilo gram":"kilogram", 32514 "Kilo grams":"kilogram", 32515 "Kilo Gram":"kilogram", 32516 "Kilo Grams":"kilogram", 32517 "stone":"stone", 32518 "st":"stone", 32519 "stones":"stone", 32520 "Stone":"stone", 32521 "metric ton":"metric-ton", 32522 "metricton":"metric-ton", 32523 "t":"metric-ton", 32524 "tonne":"metric-ton", 32525 "tonnes":"metric-ton", 32526 "Tonne":"metric-ton", 32527 "Metric Ton":"metric-ton", 32528 "MetricTon":"metric-ton", 32529 "long ton":"long-ton", 32530 "longton":"long-ton", 32531 "Longton":"long-ton", 32532 "Long ton":"long-ton", 32533 "Long Ton":"long-ton", 32534 "short ton":"short-ton", 32535 "short tons":"short-ton", 32536 "Short ton":"short-ton", 32537 "Short Ton":"short-ton", 32538 "ton":"short-ton", 32539 "tons":"short-ton", 32540 "Ton":"short-ton" 32541 }; 32542 32543 (function() { 32544 MassUnit.aliasesLower = {}; 32545 for (var a in MassUnit.aliases) { 32546 MassUnit.aliasesLower[a.toLowerCase()] = MassUnit.aliases[a]; 32547 } 32548 })(); 32549 32550 /** 32551 * Convert a mass to another measure. 32552 * @static 32553 * @param to {string} unit to convert to 32554 * @param from {string} unit to convert from 32555 * @param mass {number} amount to be convert 32556 * @returns {number|undefined} the converted amount 32557 */ 32558 MassUnit.convert = function(to, from, mass) { 32559 from = Measurement.getUnitIdCaseInsensitive(MassUnit, from) || from; 32560 to = Measurement.getUnitIdCaseInsensitive(MassUnit, to) || to; 32561 var fromRow = MassUnit.ratios[from]; 32562 var toRow = MassUnit.ratios[to]; 32563 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 32564 return undefined; 32565 } 32566 return mass * fromRow[toRow[0]]; 32567 }; 32568 32569 /** 32570 * @private 32571 * @static 32572 */ 32573 MassUnit.getMeasures = function () { 32574 return Object.keys(MassUnit.ratios); 32575 }; 32576 32577 //register with the factory method 32578 Measurement._constructors["mass"] = MassUnit; 32579 32580 32581 /*< TemperatureUnit.js */ 32582 /* 32583 * TemperatureUnit.js - Unit conversions for temperature measurements 32584 * 32585 * Copyright © 2014-2015, 2018 JEDLSoft 32586 * 32587 * Licensed under the Apache License, Version 2.0 (the "License"); 32588 * you may not use this file except in compliance with the License. 32589 * You may obtain a copy of the License at 32590 * 32591 * http://www.apache.org/licenses/LICENSE-2.0 32592 * 32593 * Unless required by applicable law or agreed to in writing, software 32594 * distributed under the License is distributed on an "AS IS" BASIS, 32595 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32596 * 32597 * See the License for the specific language governing permissions and 32598 * limitations under the License. 32599 */ 32600 32601 /* 32602 !depends 32603 Measurement.js 32604 */ 32605 32606 32607 /** 32608 * @class 32609 * Create a new Temperature measurement instance. 32610 * 32611 * @constructor 32612 * @extends Measurement 32613 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 32614 * the construction of this instance 32615 */ 32616 var TemperatureUnit = function (options) { 32617 this.unit = "celsius"; 32618 this.amount = 0; 32619 32620 this.ratios = TemperatureUnit.ratios; 32621 this.aliases = TemperatureUnit.aliases; 32622 this.aliasesLower = TemperatureUnit.aliasesLower; 32623 this.systems = TemperatureUnit.systems; 32624 32625 this.parent.call(this, options); 32626 }; 32627 32628 TemperatureUnit.prototype = new Measurement(); 32629 TemperatureUnit.prototype.parent = Measurement; 32630 TemperatureUnit.prototype.constructor = TemperatureUnit; 32631 32632 TemperatureUnit.ratios = { 32633 /* index, C K F */ 32634 "celsius": [ 1, 1, 1, 9/5 ], 32635 "kelvin": [ 2, 1, 1, 9/5 ], 32636 "fahrenheit": [ 3, 5/9, 5/9, 1 ] 32637 }; 32638 32639 /** 32640 * Return the type of this measurement. Examples are "mass", 32641 * "length", "speed", etc. Measurements can only be converted 32642 * to measurements of the same type.<p> 32643 * 32644 * The type of the units is determined automatically from the 32645 * units. For example, the unit "grams" is type "mass". Use the 32646 * static call {@link Measurement.getAvailableUnits} 32647 * to find out what units this version of ilib supports. 32648 * 32649 * @return {string} the name of the type of this measurement 32650 */ 32651 TemperatureUnit.prototype.getMeasure = function() { 32652 return "temperature"; 32653 }; 32654 32655 /** 32656 * Return a new instance of this type of measurement. 32657 * 32658 * @param {Object} params parameters to the constructor 32659 * @return {Measurement} a measurement subclass instance 32660 */ 32661 TemperatureUnit.prototype.newUnit = function(params) { 32662 return new TemperatureUnit(params); 32663 }; 32664 32665 TemperatureUnit.systems = { 32666 "metric": [ 32667 "celsius", 32668 "kelvin" 32669 ], 32670 "uscustomary": [ 32671 "fahrenheit" 32672 ], 32673 "imperial": [ 32674 "fahrenheit" 32675 ], 32676 "conversions": { 32677 "metric": { 32678 "uscustomary": { 32679 "celsius": "fahrenheit", 32680 "kelvin": "fahrenheit" 32681 }, 32682 "imperial": { 32683 "celsius": "fahrenheit", 32684 "kelvin": "fahrenheit" 32685 } 32686 }, 32687 "uscustomary": { 32688 "metric": { 32689 "fahrenheit": "celsius" 32690 } 32691 }, 32692 "imperial": { 32693 "metric": { 32694 "fahrenheit": "celsius" 32695 } 32696 } 32697 } 32698 }; 32699 32700 TemperatureUnit.aliases = { 32701 "Celsius": "celsius", 32702 "C": "celsius", 32703 "Centegrade": "celsius", 32704 "Centigrade": "celsius", 32705 "Fahrenheit": "fahrenheit", 32706 "F": "fahrenheit", 32707 "K": "kelvin", 32708 "Kelvin": "kelvin", 32709 "°F": "fahrenheit", 32710 "℉": "fahrenheit", 32711 "℃": "celsius", 32712 "°C": "celsius" 32713 }; 32714 32715 (function() { 32716 TemperatureUnit.aliasesLower = {}; 32717 for (var a in TemperatureUnit.aliases) { 32718 TemperatureUnit.aliasesLower[a.toLowerCase()] = TemperatureUnit.aliases[a]; 32719 } 32720 })(); 32721 32722 /** 32723 * Return a new measurement instance that is converted to a new 32724 * measurement unit. Measurements can only be converted 32725 * to measurements of the same type.<p> 32726 * 32727 * @param {string} to The name of the units to convert to 32728 * @return {number|undefined} the converted measurement 32729 * or undefined if the requested units are for a different 32730 * measurement type 32731 */ 32732 TemperatureUnit.prototype.convert = function(to) { 32733 if (!to || typeof(TemperatureUnit.ratios[this.normalizeUnits(to)]) === 'undefined') { 32734 return undefined; 32735 } 32736 return TemperatureUnit.convert(to, this.unit, this.amount); 32737 }; 32738 32739 /** 32740 * Convert a temperature to another measure. 32741 * @static 32742 * @param to {string} unit to convert to 32743 * @param from {string} unit to convert from 32744 * @param temperature {number} amount to be convert 32745 * @returns {number|undefined} the converted amount 32746 */ 32747 TemperatureUnit.convert = function(to, from, temperature) { 32748 var result = 0; 32749 from = Measurement.getUnitIdCaseInsensitive(TemperatureUnit, from) || from; 32750 to = Measurement.getUnitIdCaseInsensitive(TemperatureUnit, to) || to; 32751 if (from === to) { 32752 return temperature; 32753 } else if (from === "celsius") { 32754 if (to === "fahrenheit") { 32755 result = ((temperature * 9 / 5) + 32); 32756 } else if (to === "kelvin") { 32757 result = (temperature + 273.15); 32758 } 32759 } else if (from === "fahrenheit") { 32760 if (to === "celsius") { 32761 result = ((5 / 9 * (temperature - 32))); 32762 } else if (to === "kelvin") { 32763 result = ((temperature + 459.67) * 5 / 9); 32764 } 32765 } else if (from === "kelvin") { 32766 if (to === "celsius") { 32767 result = (temperature - 273.15); 32768 } else if (to === "fahrenheit") { 32769 result = ((temperature * 9 / 5) - 459.67); 32770 } 32771 } 32772 32773 return result; 32774 }; 32775 32776 /** 32777 * Scale the measurement unit to an acceptable level. The scaling 32778 * happens so that the integer part of the amount is as small as 32779 * possible without being below zero. This will result in the 32780 * largest units that can represent this measurement without 32781 * fractions. Measurements can only be scaled to other measurements 32782 * of the same type. 32783 * 32784 * @param {string=} measurementsystem system to use (uscustomary|imperial|metric), 32785 * or undefined if the system can be inferred from the current measure 32786 * @return {Measurement} a new instance that is scaled to the 32787 * right level 32788 */ 32789 TemperatureUnit.prototype.scale = function(measurementsystem) { 32790 // no scaling for temp units 32791 return this; 32792 }; 32793 32794 /** 32795 * @private 32796 * @static 32797 */ 32798 TemperatureUnit.getMeasures = function () { 32799 return ["celsius", "kelvin", "fahrenheit"]; 32800 }; 32801 32802 //register with the factory method 32803 Measurement._constructors["temperature"] = TemperatureUnit; 32804 32805 32806 /*< TimeUnit.js */ 32807 /* 32808 * TimeUnit.js - Unit conversions for time measurements 32809 * 32810 * Copyright © 2014-2015, 2018 JEDLSoft 32811 * 32812 * Licensed under the Apache License, Version 2.0 (the "License"); 32813 * you may not use this file except in compliance with the License. 32814 * You may obtain a copy of the License at 32815 * 32816 * http://www.apache.org/licenses/LICENSE-2.0 32817 * 32818 * Unless required by applicable law or agreed to in writing, software 32819 * distributed under the License is distributed on an "AS IS" BASIS, 32820 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 32821 * 32822 * See the License for the specific language governing permissions and 32823 * limitations under the License. 32824 */ 32825 32826 /* 32827 !depends 32828 Measurement.js 32829 */ 32830 32831 32832 /** 32833 * @class 32834 * Create a new time measurement instance. 32835 * 32836 * @constructor 32837 * @extends Measurement 32838 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 32839 * the construction of this instance 32840 */ 32841 var TimeUnit = function (options) { 32842 this.unit = "second"; 32843 this.amount = 0; 32844 32845 this.ratios = TimeUnit.ratios; 32846 this.aliases = TimeUnit.aliases; 32847 this.aliasesLower = TimeUnit.aliasesLower; 32848 this.systems = TimeUnit.systems; 32849 32850 this.parent.call(this, options); 32851 }; 32852 32853 TimeUnit.prototype = new Measurement(); 32854 TimeUnit.prototype.parent = Measurement; 32855 TimeUnit.prototype.constructor = TimeUnit; 32856 32857 TimeUnit.ratios = { 32858 /* index nsec msec mlsec sec min hour day week month year decade century millenium */ 32859 "nanosecond": [ 1, 1, 0.001, 1e-6, 1e-9, 1.6667e-11, 2.7778e-13, 1.1574e-14, 1.6534e-15, 3.8027e-16, 3.1689e-17, 3.1689e-18, 3.1689e-19, 3.1689e-20], 32860 "microsecond": [ 2, 1000, 1, 0.001, 1e-6, 1.6667e-8, 2.7778e-10, 1.1574e-11, 1.6534e-12, 3.8027e-13, 3.1689e-14, 3.1689e-15, 3.1689e-16, 3.1689e-17], 32861 "millisecond": [ 3, 1e+6, 1000, 1, 0.001, 1.6667e-5, 2.7778e-7, 1.1574e-8, 1.6534e-9, 3.8027e-10, 3.1689e-11, 3.1689e-12, 3.1689e-13, 3.1689e-14], 32862 "second": [ 4, 1e+9, 1e+6, 1000, 1, 0.0166667, 0.000277778, 1.1574e-5, 1.6534e-6, 3.8027e-7, 3.1689e-8, 3.1689e-9, 3.1689e-10, 3.1689e-11], 32863 "minute": [ 5, 6e+10, 6e+7, 60000, 60, 1, 0.0166667, 0.000694444, 9.9206e-5, 2.2816e-5, 1.9013e-6, 1.9013e-7, 1.9013e-8, 1.9013e-9 ], 32864 "hour": [ 6, 3.6e+12, 3.6e+9, 3.6e+6, 3600, 60, 1, 0.0416667, 0.00595238, 0.00136895, 0.00011408, 1.1408e-5, 1.1408e-6, 1.1408e-7 ], 32865 "day": [ 7, 8.64e+13, 8.64e+10, 8.64e+7, 86400, 1440, 24, 1, 0.142857, 0.0328549, 0.00273791, 0.000273791, 2.7379e-5, 2.7379e-6 ], 32866 "week": [ 8, 6.048e+14, 6.048e+11, 6.048e+8, 604800, 10080, 168, 7, 1, 0.229984, 0.0191654, 0.00191654, 0.000191654, 1.91654e-5], 32867 "month": [ 9, 2.63e+15, 2.63e+12, 2.63e+9, 2.63e+6, 43829.1, 730.484, 30.4368, 4.34812, 1, 0.0833333, 0.00833333, 0.000833333, 8.33333e-5], 32868 "year": [ 10, 3.156e+16, 3.156e+13, 3.156e+10, 3.156e+7, 525949, 8765.81, 365.242, 52.1775, 12, 1, 0.1, 0.01, 0.001 ], 32869 "decade": [ 11, 3.156e+17, 3.156e+14, 3.156e+11, 3.156e+8, 5.259e+6, 87658.1, 3652.42, 521.775, 120, 10, 1, 0.1, 0.01 ], 32870 "century": [ 12, 3.156e+18, 3.156e+18, 3.156e+12, 3.156e+9, 5.259e+7, 876581, 36524.2, 5217.75, 1200, 100, 10, 1, 0.1 ], 32871 "millenium": [ 13, 3.156e+19, 3.156e+19, 3.156e+13, 3.156e+10, 5.259e+8, 8765810, 365242, 52177.5, 12000, 1000, 100, 10, 1 ] 32872 }; 32873 32874 /** 32875 * Return the type of this measurement. Examples are "mass", 32876 * "length", "speed", etc. Measurements can only be converted 32877 * to measurements of the same type.<p> 32878 * 32879 * The type of the units is determined automatically from the 32880 * units. For example, the unit "grams" is type "mass". Use the 32881 * static call {@link Measurement.getAvailableUnits} 32882 * to find out what units this version of ilib supports. 32883 * 32884 * @return {string} the name of the type of this measurement 32885 */ 32886 TimeUnit.prototype.getMeasure = function() { 32887 return "time"; 32888 }; 32889 32890 /** 32891 * Return a new instance of this type of measurement. 32892 * 32893 * @param {Object} params parameters to the constructor 32894 * @return {Measurement} a measurement subclass instance 32895 */ 32896 TimeUnit.prototype.newUnit = function(params) { 32897 return new TimeUnit(params); 32898 }; 32899 32900 32901 TimeUnit.systems = { 32902 "metric": [ 32903 "nanosecond", 32904 "microsecond", 32905 "millisecond", 32906 "second", 32907 "minute", 32908 "hour", 32909 "day", 32910 "week", 32911 "month", 32912 "year", 32913 "decade", 32914 "century" 32915 ], 32916 "uscustomary": [ 32917 "nanosecond", 32918 "microsecond", 32919 "millisecond", 32920 "second", 32921 "minute", 32922 "hour", 32923 "day", 32924 "week", 32925 "month", 32926 "year", 32927 "decade", 32928 "century" 32929 ], 32930 "imperial": [ 32931 "nanosecond", 32932 "microsecond", 32933 "millisecond", 32934 "second", 32935 "minute", 32936 "hour", 32937 "day", 32938 "week", 32939 "month", 32940 "year", 32941 "decade", 32942 "century" 32943 ], 32944 "conversions": { 32945 "metric": {}, 32946 "uscustomary": {}, 32947 "imperial": {} 32948 } 32949 }; 32950 32951 TimeUnit.aliases = { 32952 "ns": "nanosecond", 32953 "NS": "nanosecond", 32954 "nS": "nanosecond", 32955 "Ns": "nanosecond", 32956 "Nanosecond": "nanosecond", 32957 "Nanoseconds": "nanosecond", 32958 "nanosecond": "nanosecond", 32959 "nanoseconds": "nanosecond", 32960 "NanoSecond": "nanosecond", 32961 "NanoSeconds": "nanosecond", 32962 "μs": "microsecond", 32963 "μS": "microsecond", 32964 "microsecond": "microsecond", 32965 "microseconds": "microsecond", 32966 "Microsecond": "microsecond", 32967 "Microseconds": "microsecond", 32968 "MicroSecond": "microsecond", 32969 "MicroSeconds": "microsecond", 32970 "ms": "millisecond", 32971 "MS": "millisecond", 32972 "mS": "millisecond", 32973 "Ms": "millisecond", 32974 "millisecond": "millisecond", 32975 "milliseconds": "millisecond", 32976 "Millisecond": "millisecond", 32977 "Milliseconds": "millisecond", 32978 "MilliSecond": "millisecond", 32979 "MilliSeconds": "millisecond", 32980 "s": "second", 32981 "S": "second", 32982 "sec": "second", 32983 "second": "second", 32984 "seconds": "second", 32985 "Second": "second", 32986 "Seconds": "second", 32987 "min": "minute", 32988 "Min": "minute", 32989 "minute": "minute", 32990 "minutes": "minute", 32991 "Minute": "minute", 32992 "Minutes": "minute", 32993 "h": "hour", 32994 "H": "hour", 32995 "hr": "hour", 32996 "Hr": "hour", 32997 "hR": "hour", 32998 "HR": "hour", 32999 "hour": "hour", 33000 "hours": "hour", 33001 "Hour": "hour", 33002 "Hours": "hour", 33003 "Hrs": "hour", 33004 "hrs": "hour", 33005 "day": "day", 33006 "days": "day", 33007 "Day": "day", 33008 "Days": "day", 33009 "week": "week", 33010 "weeks": "week", 33011 "Week": "week", 33012 "Weeks": "week", 33013 "month": "month", 33014 "Month": "month", 33015 "months": "month", 33016 "Months": "month", 33017 "year": "year", 33018 "years": "year", 33019 "Year": "year", 33020 "Years": "year", 33021 "yr": "year", 33022 "Yr": "year", 33023 "yrs": "year", 33024 "Yrs": "year", 33025 "decade": "decade", 33026 "decades": "decade", 33027 "Decade": "decade", 33028 "Decades": "decade", 33029 "century": "century", 33030 "centuries": "century", 33031 "Century": "century", 33032 "Centuries": "century", 33033 "millenium": "millenium", 33034 "milleniums": "millenium", 33035 "millenia": "millenium", 33036 "mill.": "millenium", 33037 "milm": "millenium" 33038 }; 33039 33040 (function() { 33041 TimeUnit.aliasesLower = {}; 33042 for (var a in TimeUnit.aliases) { 33043 TimeUnit.aliasesLower[a.toLowerCase()] = TimeUnit.aliases[a]; 33044 } 33045 })(); 33046 33047 /** 33048 * Convert a time to another measure. 33049 * @static 33050 * @param to {string} unit to convert to 33051 * @param from {string} unit to convert from 33052 * @param time {number} amount to be convert 33053 * @returns {number|undefined} the converted amount 33054 */ 33055 TimeUnit.convert = function(to, from, time) { 33056 from = Measurement.getUnitIdCaseInsensitive(TimeUnit, from) || from; 33057 to = Measurement.getUnitIdCaseInsensitive(TimeUnit, to) || to; 33058 var fromRow = TimeUnit.ratios[from]; 33059 var toRow = TimeUnit.ratios[to]; 33060 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 33061 return undefined; 33062 } 33063 return time * fromRow[toRow[0]]; 33064 }; 33065 33066 /** 33067 * @private 33068 * @static 33069 */ 33070 TimeUnit.getMeasures = function () { 33071 return Object.keys(TimeUnit.ratios); 33072 }; 33073 33074 //register with the factory method 33075 Measurement._constructors["time"] = TimeUnit; 33076 33077 33078 /*< VelocityUnit.js */ 33079 /* 33080 * VelocityUnit.js - Unit conversions for velocity/speed measurements 33081 * 33082 * Copyright © 2014-2015, 2018 JEDLSoft 33083 * 33084 * Licensed under the Apache License, Version 2.0 (the "License"); 33085 * you may not use this file except in compliance with the License. 33086 * You may obtain a copy of the License at 33087 * 33088 * http://www.apache.org/licenses/LICENSE-2.0 33089 * 33090 * Unless required by applicable law or agreed to in writing, software 33091 * distributed under the License is distributed on an "AS IS" BASIS, 33092 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33093 * 33094 * See the License for the specific language governing permissions and 33095 * limitations under the License. 33096 */ 33097 33098 /* 33099 !depends 33100 Measurement.js 33101 */ 33102 33103 33104 /** 33105 * @class 33106 * Create a new speed measurement instance. 33107 * 33108 * @constructor 33109 * @extends Measurement 33110 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 33111 * the construction of this instance 33112 */ 33113 var VelocityUnit = function (options) { 33114 this.unit = "meter-per-second"; 33115 this.amount = 0; 33116 33117 this.ratios = VelocityUnit.ratios; 33118 this.aliases = VelocityUnit.aliases; 33119 this.aliasesLower = VelocityUnit.aliasesLower; 33120 this.systems = VelocityUnit.systems; 33121 33122 this.parent.call(this, options); 33123 }; 33124 33125 VelocityUnit.prototype = new Measurement(); 33126 VelocityUnit.prototype.parent = Measurement; 33127 VelocityUnit.prototype.constructor = VelocityUnit; 33128 33129 VelocityUnit.ratios = { 33130 /* index, k/h f/s miles/h knot m/s km/s miles/s */ 33131 "kilometer-per-hour": [ 1, 1, 0.911344, 0.621371, 0.539957, 0.277778, 2.77778e-4, 1.72603109e-4 ], 33132 "foot-per-second": [ 2, 1.09728, 1, 0.681818, 0.592484, 0.3048, 3.048e-4, 1.89393939e-4 ], 33133 "mile-per-hour": [ 3, 1.60934, 1.46667, 1, 0.868976, 0.44704, 4.4704e-4, 2.77777778e-4 ], 33134 "knot": [ 4, 1.852, 1.68781, 1.15078, 1, 0.514444, 5.14444e-4, 3.19660958e-4 ], 33135 "meter-per-second": [ 5, 3.6, 3.28084, 2.236936, 1.94384, 1, 0.001, 6.21371192e-4 ], 33136 "kilometer-per-second": [ 6, 3600, 3280.8399, 2236.93629, 1943.84449, 1000, 1, 0.621371192 ], 33137 "mile-per-second": [ 7, 5793.6384, 5280, 3600, 3128.31447, 1609.344, 1.609344, 1 ] 33138 }; 33139 33140 /** 33141 * Return a new instance of this type of measurement. 33142 * 33143 * @param {Object} params parameters to the constructor 33144 * @return {Measurement} a measurement subclass instance 33145 */ 33146 VelocityUnit.prototype.newUnit = function(params) { 33147 return new VelocityUnit(params); 33148 }; 33149 33150 VelocityUnit.systems = { 33151 "metric": [ 33152 "kilometer-per-hour", 33153 "meter-per-second", 33154 "kilometer-per-second" 33155 ], 33156 "imperial": [ 33157 "foot-per-second", 33158 "mile-per-hour", 33159 "knot", 33160 "mile-per-second" 33161 ], 33162 "uscustomary": [ 33163 "foot-per-second", 33164 "mile-per-hour", 33165 "knot", 33166 "mile-per-second" 33167 ], 33168 "conversions": { 33169 "imperial": { 33170 "metric": { 33171 "mile-per-hour": "kilometer-per-hour", 33172 "foot-per-second": "meter-per-second", 33173 "mile-per-second": "kilometer-per-second", 33174 "knot": "kilometer-per-hour" 33175 } 33176 }, 33177 "uscustomary": { 33178 "metric": { 33179 "mile-per-hour": "kilometer-per-hour", 33180 "foot-per-second": "meter-per-second", 33181 "mile-per-second": "kilometer-per-second", 33182 "knot": "kilometer-per-hour" 33183 } 33184 }, 33185 "metric": { 33186 "uscustomary": { 33187 "kilometer-per-hour": "mile-per-hour", 33188 "meter-per-second": "foot-per-second", 33189 "kilometer-per-second": "mile-per-second" 33190 }, 33191 "imperial": { 33192 "kilometer-per-hour": "mile-per-hour", 33193 "meter-per-second": "foot-per-second", 33194 "kilometer-per-second": "mile-per-second" 33195 } 33196 } 33197 } 33198 }; 33199 33200 /** 33201 * Return the type of this measurement. Examples are "mass", 33202 * "length", "speed", etc. Measurements can only be converted 33203 * to measurements of the same type.<p> 33204 * 33205 * The type of the units is determined automatically from the 33206 * units. For example, the unit "grams" is type "mass". Use the 33207 * static call {@link Measurement.getAvailableUnits} 33208 * to find out what units this version of ilib supports. 33209 * 33210 * @return {string} the name of the type of this measurement 33211 */ 33212 VelocityUnit.prototype.getMeasure = function() { 33213 return "velocity"; 33214 }; 33215 33216 VelocityUnit.aliases = { 33217 "foot/sec": "foot-per-second", 33218 "foot/s": "foot-per-second", 33219 "feet/s": "foot-per-second", 33220 "f/s": "foot-per-second", 33221 "feet/second": "foot-per-second", 33222 "feet/sec": "foot-per-second", 33223 "meter/sec": "meter-per-second", 33224 "meter/s": "meter-per-second", 33225 "meters/s": "meter-per-second", 33226 "metre/sec": "meter-per-second", 33227 "metre/s": "meter-per-second", 33228 "metres/s": "meter-per-second", 33229 "mt/sec": "meter-per-second", 33230 "m/sec": "meter-per-second", 33231 "mt/s": "meter-per-second", 33232 "m/s": "meter-per-second", 33233 "mps": "meter-per-second", 33234 "meters/second": "meter-per-second", 33235 "meters/sec": "meter-per-second", 33236 "kilometer/hour": "kilometer-per-hour", 33237 "km/hour": "kilometer-per-hour", 33238 "kilometers/hour": "kilometer-per-hour", 33239 "kilometer per hour": "kilometer-per-hour", 33240 "kilometers per hour": "kilometer-per-hour", 33241 "kph": "kilometer-per-hour", 33242 "kmh": "kilometer-per-hour", 33243 "km/h": "kilometer-per-hour", 33244 "kilometer/h": "kilometer-per-hour", 33245 "kilometers/h": "kilometer-per-hour", 33246 "km/hr": "kilometer-per-hour", 33247 "kilometer/hr": "kilometer-per-hour", 33248 "kilometers/hr": "kilometer-per-hour", 33249 "kilometre/hour": "kilometer-per-hour", 33250 "mph": "mile-per-hour", 33251 "mile/hour": "mile-per-hour", 33252 "mile per hour": "mile-per-hour", 33253 "miles per hour": "miles-per-hour", 33254 "mile/hr": "mile-per-hour", 33255 "mile/h": "mile-per-hour", 33256 "miles/h": "mile-per-hour", 33257 "miles/hr": "mile-per-hour", 33258 "miles/hour": "mile-per-hour", 33259 "kn": "knot", 33260 "kt": "knot", 33261 "kts": "knot", 33262 "knots": "knot", 33263 "nm/h": "knot", 33264 "nm/hr": "knot", 33265 "nauticalmile/h": "knot", 33266 "nauticalmile/hr": "knot", 33267 "nauticalmile/hour": "knot", 33268 "nauticalmiles/hr": "knot", 33269 "nauticalmiles/hour": "knot", 33270 "nautical mile per hour": "knot", 33271 "nautical miles per hour": "knot", 33272 "nautical-mile/h": "knot", 33273 "nautical-mile/hr": "knot", 33274 "nautical-mile/hour": "knot", 33275 "nautical-miles/hr": "knot", 33276 "nautical-miles/hour": "knot", 33277 "knot": "knot", 33278 "kilometer/second": "kilometer-per-second", 33279 "kilometer/sec": "kilometer-per-second", 33280 "kilometre/sec": "kilometer-per-second", 33281 "Kilometre/sec": "kilometer-per-second", 33282 "kilometers/second": "kilometer-per-second", 33283 "kilometers/sec": "kilometer-per-second", 33284 "kilometres/sec": "kilometer-per-second", 33285 "Kilometres/sec": "kilometer-per-second", 33286 "km/sec": "kilometer-per-second", 33287 "Km/s": "kilometer-per-second", 33288 "km/s": "kilometer-per-second", 33289 "miles/second": "mile-per-second", 33290 "miles/sec": "mile-per-second", 33291 "miles/s": "mile-per-second", 33292 "mile/s": "mile-per-second", 33293 "mile/sec": "mile-per-second", 33294 "Mile/s": "mile-per-second" 33295 }; 33296 33297 (function() { 33298 VelocityUnit.aliasesLower = {}; 33299 for (var a in VelocityUnit.aliases) { 33300 VelocityUnit.aliasesLower[a.toLowerCase()] = VelocityUnit.aliases[a]; 33301 } 33302 })(); 33303 33304 /** 33305 * Convert a speed to another measure. 33306 * @static 33307 * @param to {string} unit to convert to 33308 * @param from {string} unit to convert from 33309 * @param speed {number} amount to be convert 33310 * @returns {number|undefined} the converted amount 33311 */ 33312 VelocityUnit.convert = function(to, from, speed) { 33313 from = Measurement.getUnitIdCaseInsensitive(VelocityUnit, from) || from; 33314 to = Measurement.getUnitIdCaseInsensitive(VelocityUnit, to) || to; 33315 var fromRow = VelocityUnit.ratios[from]; 33316 var toRow = VelocityUnit.ratios[to]; 33317 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 33318 return undefined; 33319 } 33320 var result = speed * fromRow[toRow[0]]; 33321 return result; 33322 }; 33323 33324 /** 33325 * @private 33326 * @static 33327 */ 33328 VelocityUnit.getMeasures = function () { 33329 return Object.keys(VelocityUnit.ratios); 33330 }; 33331 33332 //register with the factory method 33333 Measurement._constructors["speed"] = VelocityUnit; 33334 Measurement._constructors["velocity"] = VelocityUnit; 33335 33336 33337 /*< VolumeUnit.js */ 33338 /* 33339 * VolumeUnit.js - Unit conversions for volume measurements 33340 * 33341 * Copyright © 2014-2015, 2018 JEDLSoft 33342 * 33343 * Licensed under the Apache License, Version 2.0 (the "License"); 33344 * you may not use this file except in compliance with the License. 33345 * You may obtain a copy of the License at 33346 * 33347 * http://www.apache.org/licenses/LICENSE-2.0 33348 * 33349 * 33350 * Unless required by applicable law or agreed to in writing, software 33351 * distributed under the License is distributed on an "AS IS" BASIS, 33352 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33353 * 33354 * See the License for the specific language governing permissions and 33355 * limitations under the License. 33356 */ 33357 33358 /* 33359 !depends 33360 Measurement.js 33361 */ 33362 33363 33364 /** 33365 * @class 33366 * Create a new Volume measurement instance. 33367 * 33368 * @constructor 33369 * @extends Measurement 33370 * @param options {{unit:string,amount:number|string|undefined}} Options controlling 33371 * the construction of this instance 33372 */ 33373 var VolumeUnit = function (options) { 33374 this.unit = "cubic-meter"; 33375 this.amount = 0; 33376 33377 this.ratios = VolumeUnit.ratios; 33378 this.aliases = VolumeUnit.aliases; 33379 this.aliasesLower = VolumeUnit.aliasesLower; 33380 this.systems = VolumeUnit.systems; 33381 33382 this.parent.call(this, options); 33383 }; 33384 33385 VolumeUnit.prototype = new Measurement(); 33386 VolumeUnit.prototype.parent = Measurement; 33387 VolumeUnit.prototype.constructor = VolumeUnit; 33388 33389 VolumeUnit.ratios = { 33390 /* index, tsp, tbsp, cubic inch us ounce, cup, pint, quart, gallon, cubic foot, milliliter liter, cubic meter, imperial tsp, imperial tbsp, imperial ounce, imperial cup, imperial pint, imperial quart, imperial gal, cubic cm */ 33391 "teaspoon" : [1, 1, 0.3333333333333333, 0.300781, 0.166667, 0.0208333, 0.0104167, 0.00520833, 0.00130208, 0.000174063, 4.92892, 0.00492892, 4.9289e-6, 0.832674, 0.277558, 0.173474, 0.0173474, 0.00867369, 0.00433684, 0.00108421, 4.92892 ], 33392 "tablespoon": [2, 3, 1, 0.902344, 0.5, 0.0625, 0.0312, 0.015625, 0.00390625, 0.00052219, 14.7868, 0.0147868, 1.4787e-5, 2.49802, 0.832674, 0.520421, 0.0520421, 0.0260211, 0.0130105, 0.00325263, 14.7868 ], 33393 "cubic-inch": [3, 3.32468, 1.10823, 1, 0.554113, 0.0692641, 0.034632, 0.017316, 0.004329, 0.000578704, 16.3871, 0.0163871, 1.6387e-5, 2.76837, 0.92279, 0.576744, 0.057674402, 0.0288372, 0.0144186, 0.00360465, 16.3871 ], 33394 "fluid-ounce": [4, 6, 2, 1.80469, 1, 0.125, 0.0625, 0.03125, 0.0078125, 0.00104438, 29.5735, 0.0295735, 2.9574e-5, 4.99604, 1.04084, 1.04084, 0.10408427, 0.0520421, 0.0260211, 0.00650526, 29.5735 ], 33395 "cup": [5, 48, 16, 14.4375, 8, 1, 0.5, 0.25, 0.0625, 0.00835503, 236.588, 0.236588, 0.000236588, 39.9683, 13.3228, 8.32674, 0.83267418, 0.416337, 0.208168, 0.0520421, 236.588 ], 33396 "pint": [6, 96, 32, 28.875, 16, 2, 1, 0.5, 0.125, 0.0167101, 473.176, 0.473176, 0.000473176, 79.9367, 26.6456, 16.6535, 1.66534836, 0.83267418, 0.416337, 0.104084, 473.176 ], 33397 "quart": [7, 192, 64, 57.75, 32, 4, 2, 1, 0.25, 0.0334201, 946.353, 0.946353, 0.000946353, 159.873, 53.2911, 33.307, 3.33069674, 1.66534836, 0.832674, 0.208168, 946.353 ], 33398 "gallon": [8, 768, 256, 231, 128, 16, 8, 4, 1, 0.133681, 3785.41, 3.78541, 0.00378541, 639.494, 213.165, 133.228, 13.322787, 6.66139, 3.33069674, 0.832674, 3785.41 ], 33399 "cubic-foot": [9, 5745.04, 1915.01, 1728, 957.506, 119.688, 59.8442, 29.9221, 7.48052, 1, 28316.8, 28.3168, 0.0283168, 4783.74, 1594.58, 996.613, 99.661367, 49.8307, 24.9153, 6.22883, 28316.8 ], 33400 "milliliter": [10, 0.202884, 0.067628, 0.0610237, 0.033814, 0.00422675, 0.00211338, 0.00105669, 0.000264172, 3.5315e-5, 1, 0.001, 1e-6, 0.168936, 0.0563121, 0.0351951, 0.00351950797, 0.00175975, 0.000879877, 0.000219969, 1 ], 33401 "liter": [11, 202.884, 67.628, 61.0237, 33.814, 4.22675, 2.11338, 1.05669, 0.264172, 0.0353147, 1000, 1, 0.001, 56.3121, 56.3121, 35.191, 3.51950797, 1.75975, 0.879877, 0.219969, 1000 ], 33402 "cubic-meter": [12, 202884, 67628, 61023.7, 33814, 4226.75, 2113.38, 1056.69, 264.172, 35.3147, 1e+6, 1000, 1, 168936, 56312.1, 35195.1, 3519.50797, 1759.75, 879.877, 219.969, 1e+6 ], 33403 "teaspoon-imperial": [13, 1.20095, 0.200158, 0.361223, 0.600475, 0.0250198, 0.0125099, 0.00625495, 0.00156374, 0.000209041, 5.91939, 0.00591939, 5.9194e-6, 1, 0.3333333333333333, 0.208333333333333, 0.0208333333333333, 0.0104167, 0.00520833, 0.00130208, 5.91939 ], 33404 "tablespoon-imperial": [14, 3.60285, 1.20095, 1.08367, 0.600475, 0.0750594, 0.0375297, 0.0187649, 0.00469121, 0.000627124, 17.7582, 0.0177582, 1.7758e-5, 3, 1, 0.625, 0.0625, 0.03125, 0.015625, 0.00390625, 17.7582 ], 33405 "ounce-imperial": [15, 5.76456, 1.92152, 1.73387, 0.96076, 0.120095, 0.0600475, 0.0300238, 0.00750594, 0.0010034, 28.4131, 0.0284131, 2.8413e-5, 4.8, 1.6, 1, 0.1, 0.05, 0.025, 0.00625, 28.4131 ], 33406 "pint-imperial": [17, 115.291, 38.4304, 34.6774, 19.2152, 2.4019, 1.20095, 0.600475, 0.150119, 0.020068, 568.261, 0.568261, 0.000568261, 96, 32, 20, 2, 1, 0.5, 0.125, 568.261 ], 33407 "cup-imperial": [16, 57.64557, 19.2151988, 17.3387145, 9.6075994,1.20095, 0.60047496, 0.30023748, 0.07505937, 0.010033978, 284.130625, 0.28413063, 2.841306e-4, 48, 16, 10, 1, 0.5, 0.25, 0.0625, 284.130625 ], 33408 "quart-imperial": [18, 230.582, 76.8608, 69.3549, 38.4304, 4.8038, 2.4019, 1.20095, 0.300238, 0.0401359, 1136.52, 1.13652, 0.00113652, 192, 64, 40, 4, 2, 1, 0.25, 1136.52 ], 33409 "gallon-imperial": [19, 922.33, 307.443, 277.42, 153.722, 19.2152, 9.6076, 4.8038, 1.20095, 0.160544, 4546.09, 4.54609, 0.00454609, 768, 256, 160, 16, 8, 4, 1, 4546.09 ], 33410 "cubic-centimeter": [20, 0.202884, 0.067628, 0.0610237, 0.033814, 0.00422675, 0.00211338, 0.00105669, 0.000264172, 3.5315e-5, 1, 0.001, 1e-6, 0.168936, 0.0563121, 0.0351951, 0.00351950797, 0.00175975, 0.000879877, 0.000219969, 1 ] 33411 }; 33412 33413 /** 33414 * Return the type of this measurement. Examples are "mass", 33415 * "length", "speed", etc. Measurements can only be converted 33416 * to measurements of the same type.<p> 33417 * 33418 * The type of the units is determined automatically from the 33419 * units. For example, the unit "grams" is type "mass". Use the 33420 * static call {@link Measurement.getAvailableUnits} 33421 * to find out what units this version of ilib supports. 33422 * 33423 * @return {string} the name of the type of this measurement 33424 */ 33425 VolumeUnit.prototype.getMeasure = function() { 33426 return "volume"; 33427 }; 33428 33429 VolumeUnit.aliases = { 33430 "US gal": "gallon", 33431 "US gallon": "gallon", 33432 "US Gal": "gallon", 33433 "US Gallons": "gallon", 33434 "Gal(US)": "gallon", 33435 "gal(US)": "gallon", 33436 "gallon": "gallon", 33437 "gallons": "gallon", 33438 "quart": "quart", 33439 "quarts": "quart", 33440 "US quart": "quart", 33441 "US quarts": "quart", 33442 "US Quart": "quart", 33443 "US Quarts": "quart", 33444 "US qt": "quart", 33445 "Qt(US)": "quart", 33446 "qt(US)": "quart", 33447 "US pint": "pint", 33448 "US Pint": "pint", 33449 "pint": "pint", 33450 "pint(US)": "pint", 33451 "Pint(US)": "pint", 33452 "US pints": "pint", 33453 "US Pints": "pint", 33454 "pints": "pint", 33455 "pints(US)": "pint", 33456 "Pints(US)": "pint", 33457 "US cup": "cup", 33458 "US Cup": "cup", 33459 "cup(US)": "cup", 33460 "Cup(US)": "cup", 33461 "cup": "cup", 33462 "US cups": "cup", 33463 "US Cups": "cup", 33464 "cups(US)": "cup", 33465 "Cups(US)": "cup", 33466 "cups": "cup", 33467 "us ounce": "fluid-ounce", 33468 "US ounce": "fluid-ounce", 33469 "us ounces": "fluid-ounce", 33470 "US ounces": "fluid-ounce", 33471 "fluid ounce": "fluid-ounce", 33472 "fluid ounces": "fluid-ounce", 33473 "Fluid Ounce": "fluid-ounce", 33474 "Fluid Ounces": "fluid-ounce", 33475 "℥": "fluid-ounce", 33476 "US Oz": "fluid-ounce", 33477 "oz(US)": "fluid-ounce", 33478 "Oz(US)": "fluid-ounce", 33479 "US tbsp": "tablespoon", 33480 "tbsp": "tablespoon", 33481 "tbsp(US)": "tablespoon", 33482 "US tablespoon": "tablespoon", 33483 "US tsp": "teaspoon", 33484 "US teaspoon": "teaspoon", 33485 "tsp(US)": "teaspoon", 33486 "tsp": "teaspoon", 33487 "Cubic meter": "cubic-meter", 33488 "cubic meter": "cubic-meter", 33489 "Cubic metre": "cubic-meter", 33490 "cubic metre": "cubic-meter", 33491 "cu meter": "cubic-meter", 33492 "cu metre": "cubic-meter", 33493 "Cubic meters": "cubic-meter", 33494 "cubic meters": "cubic-meter", 33495 "Cubic metres": "cubic-meter", 33496 "cubic metres": "cubic-meter", 33497 "cu meters": "cubic-meter", 33498 "cu metres": "cubic-meter", 33499 "cu m": "cubic-meter", 33500 "m3": "cubic-meter", 33501 "m³": "cubic-meter", 33502 "Cubic Centimeter": "cubic-centimeter", 33503 "cubic centimeter": "cubic-centimeter", 33504 "Cubic Centimetre": "cubic-centimeter", 33505 "cubic centimetre": "cubic-centimeter", 33506 "cu centimeter": "cubic-centimeter", 33507 "cu centimetre": "cubic-centimeter", 33508 "Cubic Centimeters": "cubic-centimeter", 33509 "cubic centimeters": "cubic-centimeter", 33510 "Cubic Centimetres": "cubic-centimeter", 33511 "cubic centimetres": "cubic-centimeter", 33512 "cu centimeters": "cubic-centimeter", 33513 "cu centimetres": "cubic-centimeter", 33514 "cu cm": "cubic-centimeter", 33515 "cm3": "cubic-centimeter", 33516 "cm³": "cubic-centimeter", 33517 "cc": "cubic-centimeter", 33518 "Liter": "liter", 33519 "Liters": "liter", 33520 "liter": "liter", 33521 "liters": "liter", 33522 "L": "liter", 33523 "l": "liter", 33524 "Milliliter": "milliliter", 33525 "ML": "milliliter", 33526 "ml": "milliliter", 33527 "milliliter": "milliliter", 33528 "milliliters": "milliliter", 33529 "mL": "milliliter", 33530 "Imperial gal": "gallon-imperial", 33531 "imperial gallon": "gallon-imperial", 33532 "Imperial gallon": "gallon-imperial", 33533 "imperial gallons": "gallon-imperial", 33534 "Imperial gallons": "gallon-imperial", 33535 "gallon(imperial)": "gallon-imperial", 33536 "gallon(imp)": "gallon-imperial", 33537 "gallons(imperial)": "gallon-imperial", 33538 "gallons(imp)": "gallon-imperial", 33539 "gal(imperial)": "gallon-imperial", 33540 "gal(imp)": "gallon-imperial", 33541 "gallon (imperial)": "gallon-imperial", 33542 "gallon (imp)": "gallon-imperial", 33543 "gallons (imperial)": "gallon-imperial", 33544 "gallons (imp)": "gallon-imperial", 33545 "gal (imperial)": "gallon-imperial", 33546 "gal (imp)": "gallon-imperial", 33547 "Imperial quart": "quart-imperial", 33548 "imperial quart": "quart-imperial", 33549 "Imperial Quart": "quart-imperial", 33550 "Imperial quarts": "quart-imperial", 33551 "imperial quarts": "quart-imperial", 33552 "Imperial Quarts": "quart-imperial", 33553 "Imperial qt": "quart-imperial", 33554 "qt(Imperial)": "quart-imperial", 33555 "qt(Imp)": "quart-imperial", 33556 "qt (Imperial)": "quart-imperial", 33557 "qt (Imp)": "quart-imperial", 33558 "quart(imperial)": "quart-imperial", 33559 "quart(imp)": "quart-imperial", 33560 "quart (imperial)": "quart-imperial", 33561 "quart (imp)": "quart-imperial", 33562 "quarts(imperial)": "quart-imperial", 33563 "quarts(imp)": "quart-imperial", 33564 "quarts (imperial)": "quart-imperial", 33565 "quarts (imp)": "quart-imperial", 33566 "Imperial pint": "pint-imperial", 33567 "imperial pint": "pint-imperial", 33568 "Imperial pints": "pint-imperial", 33569 "imperial pints": "pint-imperial", 33570 "pint(Imperial)": "pint-imperial", 33571 "pints(Imperial)": "pint-imperial", 33572 "pint(Imp)": "pint-imperial", 33573 "pints(Imp)": "pint-imperial", 33574 "pint (Imperial)": "pint-imperial", 33575 "pints (Imperial)": "pint-imperial", 33576 "pint (Imp)": "pint-imperial", 33577 "pints (Imp)": "pint-imperial", 33578 "imperial cup": "cup-imperial", 33579 "Imperial Cup": "cup-imperial", 33580 "cup(imperial)": "cup-imperial", 33581 "Cup(Imperial)": "cup-imperial", 33582 "cup (imperial)": "cup-imperial", 33583 "Cup (Imperial)": "cup-imperial", 33584 "cup(imp)": "cup-imperial", 33585 "Cup(Imp)": "cup-imperial", 33586 "cup (imp)": "cup-imperial", 33587 "Cup (Imp)": "cup-imperial", 33588 "imperial cups": "cup-imperial", 33589 "Imperial Cups": "cup-imperial", 33590 "cups(imperial)": "cup-imperial", 33591 "Cups(Imperial)": "cup-imperial", 33592 "cups (imperial)": "cup-imperial", 33593 "Cups (Imperial)": "cup-imperial", 33594 "cups(imp)": "cup-imperial", 33595 "Cups(Imp)": "cup-imperial", 33596 "cups (imp)": "cup-imperial", 33597 "Cups (Imp)": "cup-imperial", 33598 "imperial oz": "ounce-imperial", 33599 "imperial ounce": "ounce-imperial", 33600 "Imperial Ounce": "ounce-imperial", 33601 "imperial ounces": "ounce-imperial", 33602 "Imperial Ounces": "ounce-imperial", 33603 "Imperial tablespoon": "tablespoon-imperial", 33604 "imperial tablespoon": "tablespoon-imperial", 33605 "tablespoon(Imperial)": "tablespoon-imperial", 33606 "tablespoon(Imp)": "tablespoon-imperial", 33607 "tablespoon (Imperial)": "tablespoon-imperial", 33608 "tablespoon (Imp)": "tablespoon-imperial", 33609 "Imperial tablespoons": "tablespoon-imperial", 33610 "imperial tablespoons": "tablespoon-imperial", 33611 "tablespoons(Imperial)": "tablespoon-imperial", 33612 "tablespoons(Imp)": "tablespoon-imperial", 33613 "tablespoons (Imperial)": "tablespoon-imperial", 33614 "tablespoons (Imp)": "tablespoon-imperial", 33615 "Imperial tbsp": "tablespoon-imperial", 33616 "imperial tbsp": "tablespoon-imperial", 33617 "tbsp(Imperial)": "tablespoon-imperial", 33618 "tbsp(Imp)": "tablespoon-imperial", 33619 "tbsp (Imperial)": "tablespoon-imperial", 33620 "tbsp (Imp)": "tablespoon-imperial", 33621 "Imperial teaspoon": "teaspoon-imperial", 33622 "imperial teaspoon": "teaspoon-imperial", 33623 "Imperial teaspoons": "teaspoon-imperial", 33624 "imperial teaspoons": "teaspoon-imperial", 33625 "Imperial tsp": "teaspoon-imperial", 33626 "imperial tsp": "teaspoon-imperial", 33627 "teaspoon(Imperial)": "teaspoon-imperial", 33628 "teaspoon(Imp)": "teaspoon-imperial", 33629 "teaspoons(Imperial)": "teaspoon-imperial", 33630 "teaspoons(Imp)": "teaspoon-imperial", 33631 "tsp(Imperial)": "teaspoon-imperial", 33632 "tsp(Imp)": "teaspoon-imperial", 33633 "teaspoon (Imperial)": "teaspoon-imperial", 33634 "teaspoon (Imp)": "teaspoon-imperial", 33635 "teaspoons (Imperial)": "teaspoon-imperial", 33636 "teaspoons (Imp)": "teaspoon-imperial", 33637 "tsp (Imperial)": "teaspoon-imperial", 33638 "tsp (Imp)": "teaspoon-imperial", 33639 "Cubic foot": "cubic-foot", 33640 "cubic foot": "cubic-foot", 33641 "Cubic Foot": "cubic-foot", 33642 "Cubic feet": "cubic-foot", 33643 "cubic Feet": "cubic-foot", 33644 "cubic ft": "cubic-foot", 33645 "ft3": "cubic-foot", 33646 "Cubic inch": "cubic-inch", 33647 "Cubic inches": "cubic-inch", 33648 "cubic inches": "cubic-inch", 33649 "cubic inch": "cubic-inch", 33650 "cubic in": "cubic-inch", 33651 "cu in": "cubic-inch", 33652 "cu inch": "cubic-inch", 33653 "inch³": "cubic-inch", 33654 "in³": "cubic-inch", 33655 "inch^3": "cubic-inch", 33656 "in^3": "cubic-inch", 33657 "c.i": "cubic-inch", 33658 "CI": "cubic-inch", 33659 "cui": "cubic-inch" 33660 }; 33661 33662 (function() { 33663 VolumeUnit.aliasesLower = {}; 33664 for (var a in VolumeUnit.aliases) { 33665 VolumeUnit.aliasesLower[a.toLowerCase()] = VolumeUnit.aliases[a]; 33666 } 33667 })(); 33668 33669 33670 /** 33671 * Convert a volume to another measure. 33672 * @static 33673 * @param to {string} unit to convert to 33674 * @param from {string} unit to convert from 33675 * @param volume {number} amount to be convert 33676 * @returns {number|undefined} the converted amount 33677 */ 33678 VolumeUnit.convert = function(to, from, volume) { 33679 from = Measurement.getUnitIdCaseInsensitive(VolumeUnit, from) || from; 33680 to = Measurement.getUnitIdCaseInsensitive(VolumeUnit, to) || to; 33681 var fromRow = VolumeUnit.ratios[from]; 33682 var toRow = VolumeUnit.ratios[to]; 33683 if (typeof(from) === 'undefined' || typeof(to) === 'undefined') { 33684 return undefined; 33685 } 33686 var result = volume * fromRow[toRow[0]]; 33687 return result; 33688 }; 33689 33690 /** 33691 * Return a new instance of this type of measurement. 33692 * 33693 * @param {Object} params parameters to the constructor 33694 * @return {Measurement} a measurement subclass instance 33695 */ 33696 VolumeUnit.prototype.newUnit = function(params) { 33697 return new VolumeUnit(params); 33698 }; 33699 33700 /** 33701 * @private 33702 * @static 33703 */ 33704 VolumeUnit.getMeasures = function () { 33705 return Object.keys(VolumeUnit.ratios); 33706 }; 33707 33708 VolumeUnit.systems = { 33709 "metric": [ 33710 "milliliter", 33711 "liter", 33712 "cubic-meter" 33713 ], 33714 "uscustomary": [ 33715 "teaspoon", 33716 "tablespoon", 33717 "cubic-inch", 33718 "fluid-ounce", 33719 "cup", 33720 "pint", 33721 "quart", 33722 "gallon", 33723 "cubic-foot" 33724 ], 33725 "imperial": [ 33726 "teaspoon-imperial", 33727 "tablespoon-imperial", 33728 "ounce-imperial", 33729 "cup-imperial", 33730 "pint-imperial", 33731 "quart-imperial", 33732 "gallon-imperial" 33733 ], 33734 "conversions": { 33735 "metric": { 33736 "uscustomary": { 33737 "milliliter": "teaspoon", 33738 "cubic-centimeter": "teaspoon", 33739 "liter": "quart", 33740 "cubic-meter": "cubic-foot" 33741 }, 33742 "imperial": { 33743 "milliliter": "teaspoon-imperial", 33744 "cubic-centimeter": "teaspoon-imperial", 33745 "liter": "quart-imperial", 33746 "cubic-meter": "gallon-imperial" 33747 } 33748 }, 33749 "imperial": { 33750 "metric": { 33751 "teaspoon-imperial": "milliliter", 33752 "tablespoon-imperial": "milliliter", 33753 "ounce-imperial": "milliliter", 33754 "pint-imperial": "liter", 33755 "quart-imperial": "liter", 33756 "gallon-imperial": "cubic-meter" 33757 }, 33758 "uscustomary": { 33759 "teaspoon-imperial": "teaspoon", 33760 "tablespoon-imperial": "tablespoon", 33761 "ounce-imperial": "fluid-ounce", 33762 "pint-imperial": "pint", 33763 "quart-imperial": "quart", 33764 "gallon-imperial": "gallon" 33765 } 33766 }, 33767 "uscustomary": { 33768 "imperial": { 33769 "teaspoon": "teaspoon-imperial", 33770 "tablespoon": "tablespoon-imperial", 33771 "cubic-inch": "tablespoon-imperial", 33772 "fluid-ounce": "ounce-imperial", 33773 "cup": "ounce-imperial", 33774 "pint": "pint-imperial", 33775 "quart": "quart-imperial", 33776 "gallon": "gallon-imperial", 33777 "cubic-foot": "gallon-imperial" 33778 }, 33779 "metric": { 33780 "teaspoon": "milliliter", 33781 "tablespoon": "milliliter", 33782 "cubic-inch": "milliliter", 33783 "fluid-ounce": "milliliter", 33784 "cup": "milliliter", 33785 "pint": "liter", 33786 "quart": "liter", 33787 "gallon": "cubic-meter", 33788 "cubic-foot": "cubic-meter" 33789 } 33790 } 33791 } 33792 }; 33793 33794 //register with the factory method 33795 Measurement._constructors["volume"] = VolumeUnit; 33796 33797 33798 33799 /*< MeasurementFactory.js */ 33800 /* 33801 * MeasurementFactory.js - Function to instantiate the appropriate subclasses of 33802 * the Measurement class. 33803 * 33804 * Copyright © 2015, JEDLSoft 33805 * 33806 * Licensed under the Apache License, Version 2.0 (the "License"); 33807 * you may not use this file except in compliance with the License. 33808 * You may obtain a copy of the License at 33809 * 33810 * http://www.apache.org/licenses/LICENSE-2.0 33811 * 33812 * Unless required by applicable law or agreed to in writing, software 33813 * distributed under the License is distributed on an "AS IS" BASIS, 33814 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33815 * 33816 * See the License for the specific language governing permissions and 33817 * limitations under the License. 33818 */ 33819 33820 /* 33821 !depends 33822 UnknownUnit.js 33823 AreaUnit.js 33824 DigitalStorageUnit.js 33825 DigitalSpeedUnit.js 33826 EnergyUnit.js 33827 FuelConsumptionUnit.js 33828 LengthUnit.js 33829 MassUnit.js 33830 TemperatureUnit.js 33831 TimeUnit.js 33832 VelocityUnit.js 33833 VolumeUnit.js 33834 Measurement.js 33835 */ 33836 33837 // TODO: make these dependencies dynamic or at least generate them in the build 33838 // These will each add themselves to Measurement._constructors[] 33839 33840 33841 /** 33842 * Create a measurement subclass instance based on a particular measure 33843 * required. The measurement is immutable once 33844 * it is created, but it can be converted to other measurements later.<p> 33845 * 33846 * The options may contain any of the following properties: 33847 * 33848 * <ul> 33849 * <li><i>amount</i> - either a numeric amount for this measurement given 33850 * as a number of the specified units, or another Measurement instance 33851 * to convert to the requested units. If converting to new units, the type 33852 * of measure between the other instance's units and the current units 33853 * must be the same. That is, you can only convert one unit of mass to 33854 * another. You cannot convert a unit of mass into a unit of length. 33855 * 33856 * <li><i>unit</i> - units of this measurement. Use the 33857 * static call {@link MeasurementFactory.getAvailableUnits} 33858 * to find out what units this version of ilib supports. If the given unit 33859 * is not a base unit, the amount will be normalized to the number of base units 33860 * and stored as that number of base units. 33861 * For example, if an instance is constructed with 1 kg, this will be converted 33862 * automatically into 1000 g, as grams are the base unit and kg is merely a 33863 * commonly used scale of grams. 33864 * </ul> 33865 * 33866 * Here are some examples of converting a length into new units. 33867 * The first method is via this factory function by passing the old measurement 33868 * in as the "amount" property.<p> 33869 * 33870 * <pre> 33871 * var measurement1 = MeasurementFactory({ 33872 * amount: 5, 33873 * units: "kilometers" 33874 * }); 33875 * var measurement2 = MeasurementFactory({ 33876 * amount: measurement1, 33877 * units: "miles" 33878 * }); 33879 * </pre> 33880 * 33881 * The value in measurement2 will end up being about 3.125 miles.<p> 33882 * 33883 * The second method uses the convert method.<p> 33884 * 33885 * <pre> 33886 * var measurement1 = MeasurementFactory({ 33887 * amount: 5, 33888 * units: "kilometers" 33889 * }); 33890 * var measurement2 = measurement1.convert("miles"); 33891 * }); 33892 * </pre> 33893 * 33894 * The value in measurement2 will again end up being about 3.125 miles. 33895 * 33896 * @static 33897 * @param {Object=} options options that control the construction of this instance 33898 */ 33899 var MeasurementFactory = function(options) { 33900 if (!options || typeof(options.unit) === 'undefined') { 33901 return undefined; 33902 } 33903 33904 var measurement, measure = undefined; 33905 33906 // first try in the existing case 33907 for (var c in Measurement._constructors) { 33908 measurement = Measurement._constructors[c]; 33909 if (Measurement.getUnitId(measurement, options.unit)) { 33910 measure = c; 33911 break; 33912 } 33913 } 33914 33915 if (!measure) { 33916 // if it wasn't found before, try again in lower case -- this may recognize incorrectly because some 33917 // units can differ only in their case like "mm" and "Mm" 33918 for (var c in Measurement._constructors) { 33919 measurement = Measurement._constructors[c]; 33920 if (typeof(Measurement.getUnitIdCaseInsensitive(measurement, options.unit)) !== 'undefined') { 33921 measure = c; 33922 break; 33923 } 33924 } 33925 } 33926 33927 if (!measure || typeof(measure) === 'undefined') { 33928 return new UnknownUnit({ 33929 unit: options.unit, 33930 amount: options.amount 33931 }); 33932 } else { 33933 return new Measurement._constructors[measure](options); 33934 } 33935 }; 33936 33937 /** 33938 * Return a list of all possible units that this version of ilib supports. 33939 * Typically, the units are given as their full names in English. Unit names 33940 * are case-insensitive. 33941 * 33942 * @static 33943 * @return {Array.<string>} an array of strings containing names of measurement 33944 * units available 33945 */ 33946 MeasurementFactory.getAvailableUnits = function () { 33947 var units = []; 33948 for (var c in Measurement._constructors) { 33949 var measure = Measurement._constructors[c]; 33950 units = units.concat(measure.getMeasures()); 33951 } 33952 return units; 33953 }; 33954 33955 33956 33957 /*< ListFmt.js */ 33958 /* 33959 * ListFmt.js - Represent a list formatter. 33960 * 33961 * Copyright © 2017, JEDLSoft 33962 * 33963 * Licensed under the Apache License, Version 2.0 (the "License"); 33964 * you may not use this file except in compliance with the License. 33965 * You may obtain a copy of the License at 33966 * 33967 * http://www.apache.org/licenses/LICENSE-2.0 33968 * 33969 * Unless required by applicable law or agreed to in writing, software 33970 * distributed under the License is distributed on an "AS IS" BASIS, 33971 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 33972 * 33973 * See the License for the specific language governing permissions and 33974 * limitations under the License. 33975 */ 33976 33977 /* 33978 !depends 33979 ilib.js 33980 Utils.js 33981 Locale.js 33982 */ 33983 33984 // !data list 33985 33986 33987 33988 /** 33989 * @class 33990 * Create a new list formatter object that formats lists of items according to 33991 * the options.<p> 33992 * 33993 * The options object can contain zero or more of the following parameters: 33994 * 33995 * <ul> 33996 * <li><i>locale</i> locale to use to format this list, or undefined to use the 33997 * default locale 33998 * 33999 * <li><i>length</i> - Specify the length of the format to use. The length is the approximate size of the 34000 * formatted string. 34001 * 34002 * <ul> 34003 * <li><i>short</i> 34004 * <li><i>medium</i> 34005 * <li><i>long</i> 34006 * <li><i>full</i> 34007 * </ul> 34008 * 34009 * <li><i>style</i> the name of style to use to format the list, or undefined 34010 * to use the default "standard" style. another style option is "units". 34011 * 34012 * <li><i>onLoad</i> - a callback function to call when the locale data is fully loaded and the address has been 34013 * parsed. When the onLoad option is given, the address formatter object 34014 * will attempt to load any missing locale data using the ilib loader callback. 34015 * When the constructor is done (even if the data is already preassembled), the 34016 * onLoad function is called with the current instance as a parameter, so this 34017 * callback can be used with preassembled or dynamic loading or a mix of the two. 34018 * 34019 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 34020 * asynchronously. If this option is given as "false", then the "onLoad" 34021 * callback must be given, as the instance returned from this constructor will 34022 * not be usable for a while. 34023 * 34024 * <li><i>loadParams</i> - an object containing parameters to pass to the 34025 * loader callback function when locale data is missing. The parameters are not 34026 * interpretted or modified in any way. They are simply passed along. The object 34027 * may contain any property/value pairs as long as the calling code is in 34028 * agreement with the loader callback function as to what those parameters mean. 34029 * </ul> 34030 * 34031 * @constructor 34032 * @param {Object} options properties that control how this formatter behaves 34033 */ 34034 var ListFmt = function(options) { 34035 this.locale = new Locale(); 34036 this.sync = true; 34037 this.style = "standard"; 34038 this.length = "short"; 34039 this.loadParams = {}; 34040 34041 if (options) { 34042 if (options.locale) { 34043 this.locale = options.locale; 34044 } 34045 34046 if (typeof(options.sync) !== 'undefined') { 34047 this.sync = !!options.sync; 34048 } 34049 34050 if (options.length) { 34051 this.length = options.length; 34052 } 34053 34054 if (options.loadParams) { 34055 this.loadParams = options.loadParams; 34056 } 34057 34058 if (options.style) { 34059 this.style = options.style; 34060 } 34061 } 34062 34063 Utils.loadData({ 34064 name: "list.json", 34065 object: ListFmt, 34066 locale: this.locale, 34067 sync: this.sync, 34068 loadParams: this.loadParams, 34069 callback: ilib.bind(this, function (fmtdata) { 34070 this.fmtdata = fmtdata; 34071 34072 if (options && typeof(options.onLoad) === 'function') { 34073 options.onLoad(this); 34074 } 34075 }) 34076 }); 34077 }; 34078 34079 /** 34080 * Format a list of strings as grammatical text that is appropriate 34081 * for the locale of this formatter. 34082 * 34083 * @param {Array.<string>} items an array of strings to format in 34084 * order that you would like them to appear 34085 * @returns {string} a string containing the list of items that 34086 * is grammatically correct for the locale of this formatter 34087 */ 34088 34089 ListFmt.prototype.format = function(items) { 34090 if (!items || (!ilib.isArray(items))) { 34091 return ""; 34092 } 34093 34094 var itemCount = items.length; 34095 var fmtTemplate, formattedList; 34096 var startFmt, middleFmt, endFmt; 34097 var i; 34098 34099 fmtTemplate = this.fmtdata[this.style][this.length] || this.fmtdata[this.style]; 34100 startFmt = fmtTemplate["start"]; 34101 middleFmt = fmtTemplate["middle"]; 34102 endFmt = fmtTemplate["end"]; 34103 34104 if (itemCount === 0) { 34105 return ""; 34106 } 34107 else if (itemCount === 1) { 34108 formattedList = items.toString(); 34109 34110 } else if ( itemCount === 2) { 34111 fmtTemplate = fmtTemplate["2"]; 34112 formattedList = fmtTemplate.replace("{0}", items[0]).replace("{1}", items[1]); 34113 34114 } else { 34115 for(i = itemCount; i >= 0 ; i--){ 34116 if (i == itemCount) { 34117 formattedList = endFmt.replace("{0}", items[itemCount-2]).replace("{1}", items[itemCount-1]); 34118 i = i-2; 34119 } else if (i == 0) { 34120 formattedList = startFmt.replace("{0}",items[i]).replace("{1}", formattedList); 34121 } 34122 else { 34123 formattedList = middleFmt.replace("{0}",items[i]).replace("{1}", formattedList); 34124 } 34125 } 34126 } 34127 return formattedList; 34128 }; 34129 34130 /** 34131 * Return the locale of this formatter. 34132 * 34133 * @returns {string} the locale of this formatter 34134 */ 34135 ListFmt.prototype.getLocale = function() { 34136 return this.locale.getSpec(); 34137 }; 34138 34139 /** 34140 * Return the style of names returned by this formatter 34141 * @return {string} the style of names returned by this formatter 34142 */ 34143 ListFmt.prototype.getStyle = function() { 34144 return this.style; 34145 }; 34146 34147 34148 /*< UnitFmt.js */ 34149 /* 34150 * UnitFmt.js - Unit formatter class 34151 * 34152 * Copyright © 2014-2015, 2018 JEDLSoft 34153 * 34154 * Licensed under the Apache License, Version 2.0 (the "License"); 34155 * you may not use this file except in compliance with the License. 34156 * You may obtain a copy of the License at 34157 * 34158 * http://www.apache.org/licenses/LICENSE-2.0 34159 * 34160 * Unless required by applicable law or agreed to in writing, software 34161 * distributed under the License is distributed on an "AS IS" BASIS, 34162 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34163 * 34164 * See the License for the specific language governing permissions and 34165 * limitations under the License. 34166 */ 34167 34168 /* 34169 !depends 34170 ilib.js 34171 Locale.js 34172 IString.js 34173 NumFmt.js 34174 Utils.js 34175 ListFmt.js 34176 Measurement.js 34177 */ 34178 34179 // !data unitfmt 34180 34181 34182 34183 // for converting ilib lengths to the ones that are supported in cldr 34184 var lenMap = { 34185 "full": "long", 34186 "long": "long", 34187 "medium": "short", 34188 "short": "short" 34189 }; 34190 34191 /** 34192 * @class 34193 * Create a new unit formatter instance. The unit formatter is immutable once 34194 * it is created, but can format as many different strings with different values 34195 * as needed with the same options. Create different unit formatter instances 34196 * for different purposes and then keep them cached for use later if you have 34197 * more than one unit string to format.<p> 34198 * 34199 * The options may contain any of the following properties: 34200 * 34201 * <ul> 34202 * <li><i>locale</i> - locale to use when formatting the units. The locale also 34203 * controls the translation of the names of the units. If the locale is 34204 * not specified, then the default locale of the app or web page will be used. 34205 * 34206 * <li><i>autoScale</i> - when true, automatically scale the amount to get the smallest 34207 * number greater than 1, where possible, possibly by converting units within the locale's 34208 * measurement system. For example, if the current locale is "en-US", and we have 34209 * a measurement containing 278 fluid ounces, then the number "278" can be scaled down 34210 * by converting the units to a larger one such as gallons. The scaled size would be 34211 * 2.17188 gallons. Since iLib does not have a US customary measure larger than gallons, 34212 * it cannot scale it down any further. If the amount is less than the smallest measure 34213 * already, it cannot be scaled down any further and no autoscaling will be applied. 34214 * Default for the autoScale property is "true", so it only needs to be specified when 34215 * you want to turn off autoscaling. 34216 * 34217 * <li><i>autoConvert</i> - automatically convert the units to the nearest appropriate 34218 * measure of the same type in the measurement system used by the locale. For example, 34219 * if a measurement of length is given in meters, but the current locale is "en-US" 34220 * which uses the US Customary system, then the nearest appropriate measure would be 34221 * "yards", and the amount would be converted from meters to yards automatically before 34222 * being formatted. Default for the autoConvert property is "true", so it only needs to 34223 * be specified when you want to turn off autoconversion. 34224 * 34225 * <li><i>usage</i> - describe the reason for the measure. For example, the usage of 34226 * a formatter may be for a "person height", which implies that certain customary units 34227 * should be used, even though other measures in the same system may be more efficient. 34228 * In US Customary measures, a person's height is traditionally given in feet and inches, 34229 * even though yards, feet and inches would be more efficient and logical.<p> 34230 * 34231 * Specifying a usage implies that the 34232 * autoScale is turned on so that the measure can be scaled to the level required for 34233 * the customary measures for the usage. Setting the usage can also implicitly set 34234 * the style, the max- and minFractionDigits, roundingMode, length, etc. if those 34235 * options are not explicitly given in this options object. If they are given, the 34236 * explicit settings override the defaults of the usage.<p> 34237 * 34238 * Usages imply that the formatter should be used with a specific type of measurement. 34239 * If the format method is called on a measurement that is of the wrong type for the 34240 * usage, it will be formatted as a regular measurement with default options.<p> 34241 * 34242 * List of usages currently supported: 34243 * <ul> 34244 * <li><i>general</i> no specific usage with no preselected measures. (Default which does not 34245 * restrict the units used for any type of measurement.) 34246 * <li><i>floorSpace</i> area of the floor of a house or building 34247 * <li><i>landArea</i> area of a piece of plot of land 34248 * <li><i>networkingSpeed</i> speed of transfer of data over a network 34249 * <li><i>audioSpeed</i> speed of transfer of audio data 34250 * <li><i>interfaceSpeed</i> speed of transfer of data over a computer interface such as a USB or SATA bus 34251 * <li><i>foodEnergy</i> amount of energy contains in food 34252 * <li><i>electricalEnergy</i> amount of energy in electricity 34253 * <li><i>heatingEnergy</i> amount of energy required to heat things such as water or home interiors 34254 * <li><i>babyHeight</i> length of a baby 34255 * <li><i>personHeight</i> height of an adult or child (not a baby) 34256 * <li><i>vehicleDistance</i> distance traveled by a vehicle or aircraft (except a boat) 34257 * <li><i>nauticalDistance</i> distance traveled by a boat 34258 * <li><i>personWeight</i> weight/mass of an adult human or larger child 34259 * <li><i>babyWeight</i> weight/mass of a baby or of small animals such as cats and dogs 34260 * <li><i>vehicleWeight</i> weight/mass of a vehicle (including a boat) 34261 * <li><i>drugWeight</i> weight/mass of a medicinal drug 34262 * <li><i>vehicleSpeed</i> speed of travel of a vehicle or aircraft (except a boat) 34263 * <li><i>nauticalSpeed</i> speed of travel of a boat 34264 * <li><i>dryFoodVolume</i> volume of a dry food substance in a recipe such as flour 34265 * <li><i>liquidFoodVolume</i> volume of a liquid food substance in a recipe such as milk 34266 * <li><i>drinkVolume</i> volume of a drink 34267 * <li><i>fuelVolume</i> volume of a vehicular fuel 34268 * <li><i>engineVolume</i> volume of an engine's combustion space 34269 * <li><i>storageVolume</i> volume of a mass storage tank 34270 * <li><i>gasVolume</i> volume of a gas such as natural gas used in a home 34271 * </ul> 34272 * 34273 * <li><i>style</i> - give the style of this formatter. This is used to 34274 * decide how to format the number and units when the number is not whole, or becomes 34275 * not whole after auto conversion and scaling. There are two basic styles 34276 * supported so far: 34277 * 34278 * <ul> 34279 * <li><i>numeric</i> - only the largest unit is used and the number is given as 34280 * decimals. Example: "5.25 lbs" 34281 * <li><i>list</i> - display the measure with a list of successively smaller-sized 34282 * units. Example: "5 lbs 4 oz" 34283 * </ul> 34284 * 34285 * The style is most useful for units which are not powers of 10 greater than the 34286 * smaller units as in the metric system, though it can be useful for metric measures 34287 * as well. Example: "2kg 381g".<p> 34288 * 34289 * The style may be set implicitly when you set the usage. For example, if the usage is 34290 * "personWeight", the style will be "numeric" and the maxFractionDigits will be 0. That 34291 * is, weight of adults and children are most often given in whole pounds. (eg. "172 lbs"). 34292 * If the usage is "babyWeight", the style will be "list", and the measures will be pounds 34293 * and ounces. (eg. "7 lbs 2 oz"). 34294 * 34295 * <li><i>length</i> - the length of the units text. This can be either "short" or "long" 34296 * with the default being "long". Example: a short units text might be "kph" and the 34297 * corresponding long units text would be "kilometers per hour". Typically, it is the 34298 * long units text that is translated per locale, though the short one may be as well. 34299 * Plurals are taken care of properly per locale as well. 34300 * 34301 * <li><i>maxFractionDigits</i> - the maximum number of digits that should appear in the 34302 * formatted output after the decimal. A value of -1 means unlimited, and 0 means only print 34303 * the integral part of the number. 34304 * 34305 * <li><i>minFractionDigits</i> - the minimum number of fractional digits that should 34306 * appear in the formatted output. If the number does not have enough fractional digits 34307 * to reach this minimum, the number will be zero-padded at the end to get to the limit. 34308 * 34309 * <li><i>significantDigits</i> - the number of significant digits that should appear 34310 * in the formatted output. If the given number is less than 1, this option will be ignored. 34311 * 34312 * <li><i>roundingMode</i> - When the maxFractionDigits or maxIntegerDigits is specified, 34313 * this property governs how the least significant digits are rounded to conform to that 34314 * maximum. The value of this property is a string with one of the following values: 34315 * <ul> 34316 * <li><i>up</i> - round away from zero 34317 * <li><i>down</i> - round towards zero. This has the effect of truncating the number 34318 * <li><i>ceiling</i> - round towards positive infinity 34319 * <li><i>floor</i> - round towards negative infinity 34320 * <li><i>halfup</i> - round towards nearest neighbour. If equidistant, round up. 34321 * <li><i>halfdown</i> - round towards nearest neighbour. If equidistant, round down. 34322 * <li><i>halfeven</i> - round towards nearest neighbour. If equidistant, round towards the even neighbour 34323 * <li><i>halfodd</i> - round towards nearest neighbour. If equidistant, round towards the odd neighbour 34324 * </ul> 34325 * Default if this is not specified is "halfup". 34326 * 34327 * <li><i>onLoad</i> - a callback function to call when the date format object is fully 34328 * loaded. When the onLoad option is given, the UnitFmt object will attempt to 34329 * load any missing locale data using the ilib loader callback. 34330 * When the constructor is done (even if the data is already preassembled), the 34331 * onLoad function is called with the current instance as a parameter, so this 34332 * callback can be used with preassembled or dynamic loading or a mix of the two. 34333 * 34334 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 34335 * asynchronously. If this option is given as "false", then the "onLoad" 34336 * callback must be given, as the instance returned from this constructor will 34337 * not be usable for a while. 34338 * 34339 * <li><i>loadParams</i> - an object containing parameters to pass to the 34340 * loader callback function when locale data is missing. The parameters are not 34341 * interpretted or modified in any way. They are simply passed along. The object 34342 * may contain any property/value pairs as long as the calling code is in 34343 * agreement with the loader callback function as to what those parameters mean. 34344 * </ul> 34345 * 34346 * Here is an example of how you might use the unit formatter to format a string with 34347 * the correct units.<p> 34348 * 34349 * 34350 * @constructor 34351 * @param {Object} options options governing the way this date formatter instance works 34352 */ 34353 var UnitFmt = function(options) { 34354 var sync = true, 34355 loadParams = undefined; 34356 34357 this.length = "long"; 34358 this.scale = true; 34359 this.measurementType = 'undefined'; 34360 this.convert = true; 34361 this.locale = new Locale(); 34362 34363 options = options || {sync: true}; 34364 34365 if (options.locale) { 34366 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 34367 } 34368 34369 if (typeof(options.sync) === 'boolean') { 34370 sync = options.sync; 34371 } 34372 34373 if (typeof(options.loadParams) !== 'undefined') { 34374 loadParams = options.loadParams; 34375 } 34376 34377 if (options.length) { 34378 this.length = lenMap[options.length] || "long"; 34379 } 34380 34381 if (typeof(options.autoScale) === 'boolean') { 34382 this.scale = options.autoScale; 34383 } 34384 34385 if (typeof(options.style) === 'string') { 34386 this.style = options.style; 34387 } 34388 34389 if (typeof(options.usage) === 'string') { 34390 this.usage = options.usage; 34391 } 34392 34393 if (typeof(options.autoConvert) === 'boolean') { 34394 this.convert = options.autoConvert; 34395 } 34396 34397 if (typeof(options.useNative) === 'boolean') { 34398 this.useNative = options.useNative; 34399 } 34400 34401 if (options.measurementSystem) { 34402 this.measurementSystem = options.measurementSystem; 34403 } 34404 34405 if (typeof (options.maxFractionDigits) !== 'undefined') { 34406 /** 34407 * @private 34408 * @type {number|undefined} 34409 */ 34410 this.maxFractionDigits = Number(options.maxFractionDigits); 34411 } 34412 if (typeof (options.minFractionDigits) !== 'undefined') { 34413 /** 34414 * @private 34415 * @type {number|undefined} 34416 */ 34417 this.minFractionDigits = Number(options.minFractionDigits); 34418 } 34419 34420 if (typeof (options.significantDigits) !== 'undefined') { 34421 /** 34422 * @private 34423 * @type {number|undefined} 34424 */ 34425 this.significantDigits = Number(options.significantDigits); 34426 } 34427 34428 /** 34429 * @private 34430 * @type {string} 34431 */ 34432 this.roundingMode = options.roundingMode || "halfup"; 34433 34434 // ensure that the plural rules are loaded before we proceed 34435 IString.loadPlurals(sync, this.locale, loadParams, ilib.bind(this, function() { 34436 Utils.loadData({ 34437 object: "UnitFmt", 34438 locale: this.locale, 34439 name: "unitfmt.json", 34440 sync: sync, 34441 loadParams: loadParams, 34442 callback: ilib.bind(this, function (format) { 34443 this.template = format["unitfmt"][this.length]; 34444 34445 if (this.usage && format.usages && format.usages[this.usage]) { 34446 // if usage is not recognized, usageInfo will be undefined, which we will use to indicate unknown usage 34447 this.usageInfo = format.usages[this.usage]; 34448 34449 // default settings for this usage, but don't override the options that were passed in 34450 if (typeof(this.maxFractionDigits) !== 'number' && typeof(this.usageInfo.maxFractionDigits) === 'number') { 34451 this.maxFractionDigits = this.usageInfo.maxFractionDigits; 34452 } 34453 if (typeof(this.minFractionDigits) !== 'number' && typeof(this.usageInfo.minFractionDigits) === 'number') { 34454 this.minFractionDigits = this.usageInfo.minFractionDigits; 34455 } 34456 if (typeof(this.significantDigits) !== 'number' && typeof(this.usageInfo.significantDigits) === 'number') { 34457 this.significantDigits = this.usageInfo.significantDigits; 34458 } 34459 if (!this.measurementSystem && this.usageInfo.system) { 34460 this.measurementSystem = this.usageInfo.system; 34461 } 34462 this.units = this.usageInfo.units; 34463 if (!this.style && this.usageInfo.style) { 34464 this.style = this.usageInfo.style; 34465 } 34466 34467 if (this.usageInfo.systems) { 34468 this.units = { 34469 metric: this.usageInfo.systems.metric.units, 34470 uscustomary: this.usageInfo.systems.uscustomary.units, 34471 imperial: this.usageInfo.systems.imperial.units 34472 }; 34473 this.numFmt = {}; 34474 this._initNumFmt(sync, loadParams, this.usageInfo.systems.metric, ilib.bind(this, function(numfmt) { 34475 this.numFmt.metric = numfmt; 34476 this._initNumFmt(sync, loadParams, this.usageInfo.systems.uscustomary, ilib.bind(this, function(numfmt) { 34477 this.numFmt.uscustomary = numfmt; 34478 this._initNumFmt(sync, loadParams, this.usageInfo.systems.imperial, ilib.bind(this, function(numfmt) { 34479 this.numFmt.imperial = numfmt; 34480 this._init(sync, loadParams, ilib.bind(this, function () { 34481 if (options && typeof(options.onLoad) === 'function') { 34482 options.onLoad(this); 34483 } 34484 })); 34485 })); 34486 })); 34487 })); 34488 } else { 34489 this._initFormatters(sync, loadParams, {}, ilib.bind(this, function() { 34490 if (options && typeof(options.onLoad) === 'function') { 34491 options.onLoad(this); 34492 } 34493 })); 34494 } 34495 } else { 34496 this._initFormatters(sync, loadParams, {}, ilib.bind(this, function() { 34497 if (options && typeof(options.onLoad) === 'function') { 34498 options.onLoad(this); 34499 } 34500 })); 34501 } 34502 }) 34503 }); 34504 })); 34505 }; 34506 34507 UnitFmt.prototype = { 34508 /** @private */ 34509 _initNumFmt: function(sync, loadParams, options, callback) { 34510 new NumFmt({ 34511 locale: this.locale, 34512 useNative: this.useNative, 34513 maxFractionDigits: typeof(this.maxFractionDigits) !== 'undefined' ? this.maxFractionDigits : options.maxFractionDigits, 34514 minFractionDigits: typeof(this.minFractionDigits) !== 'undefined' ? this.minFractionDigits : options.minFractionDigits, 34515 significantDigits: typeof(this.significantDigits) !== 'undefined' ? this.significantDigits : options.significantDigits, 34516 roundingMode: this.roundingMode || options.roundingMode, 34517 sync: sync, 34518 loadParams: loadParams, 34519 onLoad: ilib.bind(this, function (numfmt) { 34520 callback(numfmt); 34521 }) 34522 }); 34523 }, 34524 34525 _initFormatters: function(sync, loadParams, options, callback) { 34526 this._initNumFmt(sync, loadParams, {}, ilib.bind(this, function(numfmt) { 34527 this.numFmt = { 34528 metric: numfmt, 34529 uscustomary: numfmt, 34530 imperial: numfmt 34531 }; 34532 34533 this._init(sync, loadParams, callback); 34534 })); 34535 }, 34536 34537 /** @private */ 34538 _init: function(sync, loadParams, callback) { 34539 if (this.style === "list" || (this.usageInfo && this.usageInfo.systems && 34540 (this.usageInfo.systems.metric.style === "list" || 34541 this.usageInfo.systems.uscustomary.style === "list" || 34542 this.usageInfo.systems.imperial.style === "list"))) { 34543 new ListFmt({ 34544 locale: this.locale, 34545 style: "unit", 34546 sync: sync, 34547 loadParams: loadParams, 34548 onLoad: ilib.bind(this, function (listFmt) { 34549 this.listFmt = listFmt; 34550 callback(); 34551 }) 34552 }); 34553 } else { 34554 callback(); 34555 } 34556 }, 34557 34558 /** 34559 * Return the locale used with this formatter instance. 34560 * @return {Locale} the Locale instance for this formatter 34561 */ 34562 getLocale: function() { 34563 return this.locale; 34564 }, 34565 34566 /** 34567 * Return the template string that is used to format date/times for this 34568 * formatter instance. This will work, even when the template property is not explicitly 34569 * given in the options to the constructor. Without the template option, the constructor 34570 * will build the appropriate template according to the options and use that template 34571 * in the format method. 34572 * 34573 * @return {string} the format template for this formatter 34574 */ 34575 getTemplate: function() { 34576 return this.template; 34577 }, 34578 34579 /** 34580 * Convert this formatter to a string representation by returning the 34581 * format template. This method delegates to getTemplate. 34582 * 34583 * @return {string} the format template 34584 */ 34585 toString: function() { 34586 return this.getTemplate(); 34587 }, 34588 34589 /** 34590 * Return whether or not this formatter will auto-scale the units while formatting. 34591 * @returns {boolean} true if auto-scaling is turned on 34592 */ 34593 getScale: function() { 34594 return this.scale; 34595 }, 34596 34597 /** 34598 * Return the measurement system that is used for this formatter. 34599 * @returns {string} the measurement system used in this formatter 34600 */ 34601 getMeasurementSystem: function() { 34602 return this.measurementSystem; 34603 }, 34604 34605 /** 34606 * @private 34607 */ 34608 _format: function(u, system) { 34609 var unit = u.getUnit() === "long-ton" ? "ton" : u.getUnit(); 34610 var formatted = new IString(this.template[unit]); 34611 // make sure to use the right plural rules 34612 formatted.setLocale(this.locale, true, undefined, undefined); 34613 var rounded = this.numFmt[system].constrain(u.amount); 34614 formatted = formatted.formatChoice(rounded, {n: this.numFmt[system].format(u.amount)}); 34615 return formatted.length > 0 ? formatted : rounded + " " + u.unit; 34616 }, 34617 34618 /** 34619 * Format a particular unit instance according to the settings of this 34620 * formatter object. 34621 * 34622 * @param {Measurement} measurement measurement to format 34623 * @return {string} the formatted version of the given date instance 34624 */ 34625 format: function (measurement) { 34626 var u = measurement, system, listStyle; 34627 var doScale = this.scale; 34628 34629 if (this.convert) { 34630 if (this.measurementSystem) { 34631 if (this.measurementSystem !== measurement.getMeasurementSystem()) { 34632 u = u.convertSystem(this.measurementSystem); 34633 } 34634 } else if (!this.usageInfo || Measurement.getMeasurementSystemForLocale(this.locale) !== u.getMeasurementSystem()) { 34635 u = measurement.localize(this.locale); 34636 } 34637 34638 doScale = (this.usageInfo && measurement.getMeasurementSystem() !== u.getMeasurementSystem()) || this.scale; 34639 } 34640 34641 system = u.getMeasurementSystem() || this.getMeasurementSystem() || "metric"; 34642 listStyle = (this.style === "list" || (this.usageInfo && this.usageInfo.systems && this.usageInfo.systems[system].style === "list")); 34643 34644 if (doScale) { 34645 if (this.usageInfo && measurement.getMeasure() === this.usageInfo.type && !listStyle) { 34646 // scaling with a restricted set of units 34647 u = u.scale(system, this.units); 34648 } else { 34649 u = u.scale(); // scale within the current system 34650 } 34651 } 34652 34653 if (listStyle) { 34654 var numFmt = this.numFmt[system]; 34655 u = u.expand(undefined, this.units, ilib.bind(numFmt, numFmt.constrain), this.scale); 34656 var formatted = u.map(ilib.bind(this, function(unit) { 34657 return this._format(unit, system); 34658 })); 34659 if (this.listFmt && formatted.length) { 34660 return this.listFmt.format(formatted); 34661 } else { 34662 return formatted.join(' '); 34663 } 34664 } else { 34665 return this._format(u, system); 34666 } 34667 } 34668 }; 34669 34670 34671 /*< Charset.js */ 34672 /* 34673 * Charset.js - Return information about a particular character set 34674 * 34675 * Copyright © 2014-2015, JEDLSoft 34676 * 34677 * Licensed under the Apache License, Version 2.0 (the "License"); 34678 * you may not use this file except in compliance with the License. 34679 * You may obtain a copy of the License at 34680 * 34681 * http://www.apache.org/licenses/LICENSE-2.0 34682 * 34683 * Unless required by applicable law or agreed to in writing, software 34684 * distributed under the License is distributed on an "AS IS" BASIS, 34685 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34686 * 34687 * See the License for the specific language governing permissions and 34688 * limitations under the License. 34689 */ 34690 34691 // !depends ilib.js Utils.js 34692 // !data charsetaliases charset/ISO-8859-1 charset/ISO-8859-15 charset/UTF-8 34693 34694 34695 /** 34696 * @class 34697 * Create a new character set info instance. Charset instances give information about 34698 * a particular character set, such as whether or not it is single byte or multibyte, 34699 * and which languages commonly use that charset.<p> 34700 * 34701 * The optional options object holds extra parameters if they are necessary. The 34702 * current list of supported options are: 34703 * 34704 * <ul> 34705 * <li><i>name</i> - the name of the charset. This can be given as any commonly 34706 * used name for the character set, which is normalized to a standard IANA name 34707 * before its info is loaded. If a name is not given, 34708 * this class will return information about the base character set of Javascript, 34709 * which is currently Unicode as encoded in UTF-16. 34710 * 34711 * <li><i>onLoad</i> - a callback function to call when this object is fully 34712 * loaded. When the onLoad option is given, this class will attempt to 34713 * load any missing data using the ilib loader callback. 34714 * When the constructor is done (even if the data is already preassembled), the 34715 * onLoad function is called with the current instance as a parameter, so this 34716 * callback can be used with preassembled or dynamic loading or a mix of the two. 34717 * 34718 * <li><i>sync</i> - tell whether to load any missing data synchronously or 34719 * asynchronously. If this option is given as "false", then the "onLoad" 34720 * callback must be given, because the instance returned from this constructor will 34721 * not be usable for a while. 34722 * 34723 * <li><i>loadParams</i> - an object containing parameters to pass to the 34724 * loader callback function when data is missing. The parameters are not 34725 * interpretted or modified in any way. They are simply passed along. The object 34726 * may contain any property/value pairs as long as the calling code is in 34727 * agreement with the loader callback function as to what those parameters mean. 34728 * </ul> 34729 * 34730 * If this copy of ilib is pre-assembled and all the data is already available, 34731 * or if the data was already previously loaded, then this constructor will call 34732 * the onLoad callback immediately when the initialization is done. 34733 * If the onLoad option is not given, this class will only attempt to load any 34734 * missing data synchronously. 34735 * 34736 * Depends directive: !depends charset.js 34737 * 34738 * @constructor 34739 * @see {ilib.setLoaderCallback} for information about registering a loader callback instance 34740 * @param {Object=} options options which govern the construction of this instance 34741 */ 34742 var Charset = function(options) { 34743 var sync = true, 34744 loadParams = undefined; 34745 this.originalName = "UTF-8"; 34746 34747 if (options) { 34748 if (typeof(options.name) !== 'undefined') { 34749 this.originalName = options.name; 34750 } 34751 34752 if (typeof(options.sync) !== 'undefined') { 34753 sync = !!options.sync; 34754 } 34755 34756 if (typeof(options.loadParams) !== 'undefined') { 34757 loadParams = options.loadParams; 34758 } 34759 } 34760 34761 // default data. A majority of charsets use this info 34762 this.info = { 34763 description: "default", 34764 min: 1, 34765 max: 1, 34766 bigendian: true, 34767 scripts: ["Latn"], 34768 locales: ["*"] 34769 }; 34770 34771 Utils.loadData({ 34772 object: "Charset", 34773 locale: "-", 34774 nonlocale: true, 34775 name: "charsetaliases.json", 34776 sync: sync, 34777 loadParams: loadParams, 34778 callback: ilib.bind(this, function (info) { 34779 // first map the given original name to one of the standardized IANA names 34780 if (info) { 34781 // recognize better by getting rid of extraneous crap and upper-casing 34782 // it so that the match is case-insensitive 34783 var n = this.originalName.replace(/[-_,:\+\.\(\)]/g, '').toUpperCase(); 34784 this.name = info[n]; 34785 } 34786 if (!this.name) { 34787 this.name = this.originalName; 34788 } 34789 Utils.loadData({ 34790 object: "Charset", 34791 locale: "-", 34792 nonlocale: true, 34793 name: "charset/" + this.name + ".json", 34794 sync: sync, 34795 loadParams: loadParams, 34796 callback: ilib.bind(this, function (info) { 34797 if (info) { 34798 ilib.extend(this.info, info); 34799 } 34800 if (options && typeof(options.onLoad) === 'function') { 34801 options.onLoad(this); 34802 } 34803 }) 34804 }); 34805 }) 34806 }); 34807 }; 34808 34809 Charset.prototype = { 34810 /** 34811 * Return the standard normalized name of this charset. The list of standard names 34812 * comes from the IANA registry of character set names at 34813 * <a href="http://www.iana.org/assignments/character-sets/character-sets.xhtml">http://www.iana.org/assignments/character-sets/character-sets.xhtml</a>. 34814 * 34815 * @returns {string} the name of the charset 34816 */ 34817 getName: function () { 34818 return this.name; 34819 }, 34820 34821 /** 34822 * Return the original name that this instance was constructed with before it was 34823 * normalized to the standard name returned by {@link #getName}. 34824 * 34825 * @returns {string} the original name that this instance was constructed with 34826 */ 34827 getOriginalName: function() { 34828 return this.originalName; 34829 }, 34830 34831 /** 34832 * Return a short description of the character set. 34833 * 34834 * @returns {string} a description of the character set 34835 */ 34836 getDescription: function() { 34837 return this.info.description || this.getName(); 34838 }, 34839 34840 /** 34841 * Return the smallest number of bytes that a single character in this charset 34842 * could use. For most charsets, this is 1, but for some charsets such as Unicode 34843 * encoded in UTF-16, this may be 2 or more. 34844 * @returns {number} the smallest number of bytes that a single character in 34845 * this charset uses 34846 */ 34847 getMinCharWidth: function () { 34848 return this.info.min; 34849 }, 34850 34851 /** 34852 * Return the largest number of bytes that a single character in this charset 34853 * could use. 34854 * @returns {number} the largest number of bytes that a single character in 34855 * this charset uses 34856 */ 34857 getMaxCharWidth: function () { 34858 return this.info.max; 34859 }, 34860 34861 /** 34862 * Return true if this is a multibyte character set, or false for a fixed 34863 * width character set. A multibyte character set is one in which the characters 34864 * have a variable width. That is, one character may use 1 byte and a different 34865 * character might use 2 or 3 bytes. 34866 * 34867 * @returns {boolean} true if this is a multibyte charset, or false otherwise 34868 */ 34869 isMultibyte: function() { 34870 return this.getMaxCharWidth() > this.getMinCharWidth(); 34871 }, 34872 34873 /** 34874 * Return whether or not characters larger than 1 byte use the big endian order 34875 * or little endian. 34876 * 34877 * @returns {boolean} true if this character set uses big endian order, or false 34878 * otherwise 34879 */ 34880 isBigEndian: function() { 34881 return this.info.bigendian; 34882 }, 34883 34884 /** 34885 * Return an array of ISO script codes whose characters can be encoded with this 34886 * character set. 34887 * 34888 * @returns {Array.<string>} an array of ISO script codes supported by this charset 34889 */ 34890 getScripts: function() { 34891 return this.info.scripts; 34892 } 34893 }; 34894 34895 34896 /*< Charmap.js */ 34897 /* 34898 * Charmap.js - A character set mapping class 34899 * 34900 * Copyright © 2014-2015,2018, JEDLSoft 34901 * 34902 * Licensed under the Apache License, Version 2.0 (the "License"); 34903 * you may not use this file except in compliance with the License. 34904 * You may obtain a copy of the License at 34905 * 34906 * http://www.apache.org/licenses/LICENSE-2.0 34907 * 34908 * Unless required by applicable law or agreed to in writing, software 34909 * distributed under the License is distributed on an "AS IS" BASIS, 34910 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 34911 * 34912 * See the License for the specific language governing permissions and 34913 * limitations under the License. 34914 */ 34915 34916 // !depends ilib.js JSUtils.js IString.js 34917 34918 // !data charset/US-ASCII charset/ISO-10646-UCS-2 charset/ISO-10646-UCS-4 charset/ISO-10646-Unicode-Latin1 34919 34920 34921 /** 34922 * @class 34923 * Create a new default character set mapping instance. This class is the parent 34924 * class of all of the charmapping subclasses, and only implements basic US-ASCII 34925 * mapping. The subclasses implement all other charsets, some algorithmically, and 34926 * some in a table-based way. Use {@link CharmapFactory} to create the correct 34927 * subclass instance for the desired charmap.<p> 34928 * 34929 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 34930 * character set and encoding used by Javascript itself. In order to convert 34931 * between two non-Unicode character sets, you must chain two charmap instances together 34932 * to first map to Unicode and then back to the second charset. <p> 34933 * 34934 * The options parameter controls which mapping is constructed and its behaviours. The 34935 * current list of supported options are: 34936 * 34937 * <ul> 34938 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 34939 * character. For example, if you are mapping Unicode characters to a particular native 34940 * character set that does not support particular Unicode characters, the mapper will 34941 * follow the behaviour specified in this property. Valid values are: 34942 * <ul> 34943 * <li><i>skip</i> - skip any characters that do not exist in the target charset 34944 * <li><i>placeholder</i> - put a static placeholder character in the output string 34945 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 34946 * parameter to specify which character to use in this case 34947 * <li><i>escape</i> - use an escape sequence to represent the unknown character 34948 * </ul> 34949 * The default value for the missing property if not otherwise specified is "escape" 34950 * so that information is not lost. 34951 * 34952 * <li><i>placeholder</i> - specify the placeholder character to use when the 34953 * mapper cannot map a particular input character to the output string. If this 34954 * option is not specified, then the '?' (question mark) character is used where 34955 * possible. 34956 * 34957 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 34958 * escape unknown characters in the input when mapping to native, and what 34959 * style of espcae sequences should be parsed when mapping to Unicode. Valid 34960 * values are: 34961 * <ul> 34962 * <li><i>html</i> - Escape the characters as HTML entities. This would use 34963 * the standard HTML 5.0 (or later) entity names where possible, and numeric 34964 * entities in all other cases. Eg. an "e" with an acute accent would be 34965 * "é" 34966 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 34967 * accent would be "\u00E9". This can also be specified as "c#" as 34968 * it uses a similar escape syntax. 34969 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 34970 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 34971 * acute accent would be "\x00E9". This can also be specified as "c++". 34972 * <li><i>java</i> - Use the Java escape style. This is very similar to the 34973 * the Javascript style, but the backslash has to be escaped twice. Eg. an 34974 * "e" with an acute accent would be "\\u00E9". This can also be specified 34975 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 34976 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 34977 * accent would be "\N{U+00E9}" 34978 * </ul> 34979 * The default if this style is not specified is "js" for Javascript. 34980 * </ul> 34981 * 34982 * If this copy of ilib is pre-assembled and all the data is already available, 34983 * or if the data was already previously loaded, then this constructor will call 34984 * the onLoad callback immediately when the initialization is done. 34985 * If the onLoad option is not given, this class will only attempt to load any 34986 * missing data synchronously. 34987 * 34988 * @constructor 34989 * @param {Object=} options options which govern the construction of this instance 34990 */ 34991 var Charmap = function(options) { 34992 if (options && options.noinstance) { 34993 return; 34994 } 34995 34996 this.missing = "placeholder"; 34997 this.placeholder = "?"; 34998 this.escapeStyle = "js"; 34999 this.expansionFactor = 1; 35000 35001 if (options) { 35002 if (typeof(options.placeholder) !== 'undefined') { 35003 this.placeholder = options.placeholder; 35004 } 35005 35006 var escapes = { 35007 "html": "html", 35008 "js": "js", 35009 "c#": "js", 35010 "c": "c", 35011 "c++": "c", 35012 "java": "java", 35013 "ruby": "java", 35014 "perl": "perl" 35015 }; 35016 35017 if (typeof(options.escapeStyle) !== 'undefined') { 35018 if (typeof(escapes[options.escapeStyle]) !== 'undefined') { 35019 this.escapeStyle = escapes[options.escapeStyle]; 35020 } 35021 } 35022 35023 if (typeof(options.missing) !== 'undefined') { 35024 if (options.missing === "skip" || options.missing === "placeholder" || options.missing === "escape") { 35025 this.missing = options.missing; 35026 } 35027 } 35028 } 35029 }; 35030 35031 /** 35032 * A place for the algorithmic conversions to register themselves as 35033 * they are defined. 35034 * 35035 * @static 35036 * @private 35037 */ 35038 Charmap._algorithms = {}; 35039 35040 Charmap.prototype = { 35041 /** 35042 * Return the standard name of this charmap. All charmaps map from 35043 * Unicode to the native charset, so the name returned from this 35044 * function corresponds to the native charset. 35045 * 35046 * @returns {string} the name of the locale's language in English 35047 */ 35048 getName: function () { 35049 return this.charset.getName(); 35050 }, 35051 35052 /** 35053 * @private 35054 */ 35055 writeNative: function (array, start, value) { 35056 // console.log("Charmap.writeNative: start " + start + " adding " + JSON.stringify(value)); 35057 if (ilib.isArray(value)) { 35058 for (var i = 0; i < value.length; i++) { 35059 array[start+i] = value[i]; 35060 } 35061 35062 return value.length; 35063 } else { 35064 array[start] = value; 35065 return 1; 35066 } 35067 }, 35068 35069 /** 35070 * @private 35071 */ 35072 writeNativeString: function (array, start, string) { 35073 // console.log("Charmap.writeNativeString: start " + start + " adding " + JSON.stringify(string)); 35074 for (var i = 0; i < string.length; i++) { 35075 array[start+i] = string.charCodeAt(i); 35076 } 35077 return string.length; 35078 }, 35079 35080 /** 35081 * @private 35082 */ 35083 _calcExpansionFactor: function() { 35084 var factor = 1; 35085 factor = Math.max(factor, this.charset.getMaxCharWidth()); 35086 switch (this.missing) { 35087 case "placeholder": 35088 if (this.placeholder) { 35089 factor = Math.max(factor, this.placeholder.length); 35090 } 35091 break; 35092 case "escape": 35093 switch (this.escapeStyle) { 35094 case "html": 35095 factor = Math.max(factor, 8); // HHHH; 35096 break; 35097 case "c": 35098 factor = Math.max(factor, 6); // \xHHHH 35099 break; 35100 case "perl": 35101 factor = Math.max(factor, 10); // \N{U+HHHH} 35102 break; 35103 35104 default: 35105 factor = Math.max(factor, 6); // \uHHHH 35106 break; 35107 } 35108 break; 35109 default: 35110 break; 35111 } 35112 35113 this.expansionFactor = factor; 35114 }, 35115 35116 /** 35117 * @private 35118 */ 35119 dealWithMissingChar: function(c) { 35120 var seq = ""; 35121 35122 switch (this.missing) { 35123 case "skip": 35124 // do nothing 35125 break; 35126 35127 case "escape": 35128 var num = (typeof(c) === 'string') ? c.charCodeAt(0) : c; 35129 var bigc = JSUtils.pad(num.toString(16), 4).toUpperCase(); 35130 switch (this.escapeStyle) { 35131 case "html": 35132 seq = "" + bigc + ";"; 35133 break; 35134 case "c": 35135 seq = "\\x" + bigc; 35136 break; 35137 case "java": 35138 seq = "\\\\u" + bigc; 35139 break; 35140 case "perl": 35141 seq = "\\N{U+" + bigc + "}"; 35142 break; 35143 35144 default: 35145 case "js": 35146 seq = "\\u" + bigc; 35147 break; 35148 } 35149 break; 35150 35151 default: 35152 case "placeholder": 35153 seq = this.placeholder; 35154 break; 35155 } 35156 35157 return seq; 35158 }, 35159 35160 /** 35161 * Map a string to the native character set. This string may be 35162 * given as an intrinsic Javascript string object or an IString 35163 * object. 35164 * 35165 * @param {string|IString} string string to map to a different 35166 * character set. 35167 * @return {Uint8Array} An array of bytes representing the string 35168 * in the native character set 35169 */ 35170 mapToNative: function(string) { 35171 if (!string) { 35172 return new Uint8Array(0); 35173 } 35174 35175 if (this.algorithm) { 35176 return this.algorithm.mapToNative(string); 35177 } 35178 35179 // the default algorithm is plain old ASCII 35180 var str = (string instanceof IString) ? string : new IString(string); 35181 35182 // use IString's iterator so that we take care of walking through 35183 // the code points correctly, including the surrogate pairs 35184 var c, i = 0, it = str.iterator(); 35185 var ret = new Uint8Array(str.length * this.expansionFactor); 35186 35187 while (it.hasNext() && i < ret.length) { 35188 c = it.next(); 35189 if (c < 127) { 35190 ret[i++] = c; 35191 } else { 35192 i += this.writeNativeString(ret, i, this.dealWithMissingChar(c)); 35193 } 35194 } 35195 35196 return ret; 35197 }, 35198 35199 /** 35200 * Map a native string to the standard Javascript charset of UTF-16. 35201 * This string may be given as an array of numbers where each number 35202 * represents a code point in the "from" charset, or as a Uint8Array 35203 * array of bytes representing the bytes of the string in order. 35204 * 35205 * @param {Array.<number>|Uint8Array} bytes bytes to map to 35206 * a Unicode string 35207 * @return {string} A string in the standard Javascript charset UTF-16 35208 */ 35209 mapToUnicode: function(bytes) { 35210 var ret = ""; 35211 var c, i = 0; 35212 35213 while (i < bytes.length) { 35214 c = bytes[i]; 35215 35216 // the default algorithm is plain old ASCII 35217 if (c < 128) { 35218 ret += String.fromCharCode(c); 35219 } else { 35220 // The byte at "i" wasn't ASCII 35221 ret += this.dealWithMissingChar(bytes[i++]); 35222 } 35223 } 35224 35225 return ret; 35226 } 35227 }; 35228 35229 35230 /*< CharmapTable.js */ 35231 /* 35232 * CharmapTable.js - A character set mapping class that maps using trie table 35233 * 35234 * Copyright © 2014-2015,2018, JEDLSoft 35235 * 35236 * Licensed under the Apache License, Version 2.0 (the "License"); 35237 * you may not use this file except in compliance with the License. 35238 * You may obtain a copy of the License at 35239 * 35240 * http://www.apache.org/licenses/LICENSE-2.0 35241 * 35242 * Unless required by applicable law or agreed to in writing, software 35243 * distributed under the License is distributed on an "AS IS" BASIS, 35244 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35245 * 35246 * See the License for the specific language governing permissions and 35247 * limitations under the License. 35248 */ 35249 35250 // !depends ilib.js Utils.js Charset.js Charmap.js IString.js 35251 35252 // !data charmaps/ISO-8859-1 charset/ISO-8859-1 35253 35254 35255 /** 35256 * @class 35257 * Create a new character set mapping instance using based on a trie table. Charmap 35258 * instances map strings to 35259 * other character sets. The charsets can be of any type, single-byte, multi-byte, 35260 * shifting, etc. <p> 35261 * 35262 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 35263 * character set and encoding used by Javascript itself. In order to convert 35264 * between two non-Unicode character sets, you must chain two charmap instances together 35265 * to first map to Unicode and then back to the second charset. <p> 35266 * 35267 * The options parameter controls which mapping is constructed and its behaviours. The 35268 * current list of supported options are: 35269 * 35270 * <ul> 35271 * <li><i>charset</i> - the name of the native charset to map to or from. This can be 35272 * given as an {@link Charset} instance or as a string that contains any commonly used name 35273 * for the character set, which is normalized to a standard IANA name. 35274 * If a name is not given, this class will default to the Western European character 35275 * set called ISO-8859-15. 35276 * 35277 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 35278 * character. For example, if you are mapping Unicode characters to a particular native 35279 * character set that does not support particular Unicode characters, the mapper will 35280 * follow the behaviour specified in this property. Valid values are: 35281 * <ul> 35282 * <li><i>skip</i> - skip any characters that do not exist in the target charset 35283 * <li><i>placeholder</i> - put a static placeholder character in the output string 35284 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 35285 * parameter to specify which character to use in this case 35286 * <li><i>escape</i> - use an escape sequence to represent the unknown character 35287 * </ul> 35288 * The default value for the missing property if not otherwise specified is "escape" 35289 * so that information is not lost. 35290 * 35291 * <li><i>placeholder</i> - specify the placeholder character to use when the 35292 * mapper cannot map a particular input character to the output string. If this 35293 * option is not specified, then the '?' (question mark) character is used where 35294 * possible. 35295 * 35296 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 35297 * escape unknown characters in the input when mapping to native, and what 35298 * style of espcae sequences should be parsed when mapping to Unicode. Valid 35299 * values are: 35300 * <ul> 35301 * <li><i>html</i> - Escape the characters as HTML entities. This would use 35302 * the standard HTML 5.0 (or later) entity names where possible, and numeric 35303 * entities in all other cases. Eg. an "e" with an acute accent would be 35304 * "é" 35305 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 35306 * accent would be "\u00E9". This can also be specified as "c#" as 35307 * it uses a similar escape syntax. 35308 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 35309 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 35310 * acute accent would be "\x00E9". This can also be specified as "c++". 35311 * <li><i>java</i> - Use the Java escape style. This is very similar to the 35312 * the Javascript style, but the backslash has to be escaped twice. Eg. an 35313 * "e" with an acute accent would be "\\u00E9". This can also be specified 35314 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 35315 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 35316 * accent would be "\N{U+00E9}" 35317 * </ul> 35318 * The default if this style is not specified is "js" for Javascript. 35319 * 35320 * <li><i>onLoad</i> - a callback function to call when this object is fully 35321 * loaded. When the onLoad option is given, this class will attempt to 35322 * load any missing data using the ilib loader callback. 35323 * When the constructor is done (even if the data is already preassembled), the 35324 * onLoad function is called with the current instance as a parameter, so this 35325 * callback can be used with preassembled or dynamic loading or a mix of the two. 35326 * 35327 * <li><i>sync</i> - tell whether to load any missing data synchronously or 35328 * asynchronously. If this option is given as "false", then the "onLoad" 35329 * callback must be given, because the instance returned from this constructor will 35330 * not be usable for a while. 35331 * 35332 * <li><i>loadParams</i> - an object containing parameters to pass to the 35333 * loader callback function when data is missing. The parameters are not 35334 * interpretted or modified in any way. They are simply passed along. The object 35335 * may contain any property/value pairs as long as the calling code is in 35336 * agreement with the loader callback function as to what those parameters mean. 35337 * </ul> 35338 * 35339 * If this copy of ilib is pre-assembled and all the data is already available, 35340 * or if the data was already previously loaded, then this constructor will call 35341 * the onLoad callback immediately when the initialization is done. 35342 * If the onLoad option is not given, this class will only attempt to load any 35343 * missing data synchronously. 35344 * 35345 * @constructor 35346 * @see {ilib.setLoaderCallback} for information about registering a loader callback instance 35347 * @extends Charmap 35348 * @param {Object=} options options which govern the construction of this instance 35349 */ 35350 var CharmapTable = function(options) { 35351 var sync = true; 35352 35353 // console.log("CharmapTable: constructor with options: " + JSON.stringify(options)); 35354 35355 this.parent.call(this, options); 35356 this.charsetName = "ISO-8859-15"; 35357 35358 if (options) { 35359 if (typeof(options.charset) === "object") { 35360 this.charset = options.charset; 35361 this.charsetName = this.charset.getName(); 35362 } else if (typeof(options.name) !== 'undefined') { 35363 this.charsetName = options.name; 35364 } 35365 } else { 35366 options = {sync: true}; 35367 } 35368 35369 if (!this.charset) { 35370 new Charset({ 35371 name: this.charsetName, 35372 sync: sync, 35373 loadParams: options.loadParams, 35374 onLoad: ilib.bind(this, function(cs) { 35375 this.charset = cs; 35376 this._init(options); 35377 }) 35378 }); 35379 } else { 35380 this._init(options); 35381 } 35382 }; 35383 35384 CharmapTable.prototype = new Charmap({noinstance: true}); 35385 CharmapTable.prototype.parent = Charmap; 35386 CharmapTable.prototype.constructor = CharmapTable; 35387 35388 /** 35389 * Initialize the table charmap object 35390 * @private 35391 */ 35392 CharmapTable.prototype._init = function(options) { 35393 this._calcExpansionFactor(); 35394 35395 Utils.loadData({ 35396 object: "Charmap", 35397 locale: "-", 35398 nonlocale: true, 35399 name: "charmaps/" + this.charset.getName() + ".json", 35400 sync: options.sync, 35401 loadParams: options.loadParams, 35402 callback: ilib.bind(this, function (mapping) { 35403 var ret = this; 35404 if (!mapping) { 35405 if (options.sync) { 35406 throw "No mapping found for " + this.charset.getName(); 35407 } else { 35408 ret = undefined; 35409 } 35410 } 35411 35412 /** @type {{from:Object,to:Object}} */ 35413 this.map = mapping; 35414 if (typeof(options.onLoad) === 'function') { 35415 options.onLoad(ret); 35416 } 35417 }) 35418 }); 35419 }; 35420 35421 /** 35422 * Walk a trie to find the value for the current position in the given array. 35423 * @private 35424 */ 35425 CharmapTable.prototype._trieWalk = function(trie, array, start) { 35426 function isValue(node) { 35427 return (typeof(node) === 'string' || typeof(node) === 'number' || 35428 (typeof(node) === 'object' && ilib.isArray(node))); 35429 } 35430 35431 var lastLeaf = undefined, 35432 i = start, 35433 trienode = trie; 35434 35435 while (i < array.length) { 35436 if (typeof(trienode.__leaf) !== 'undefined') { 35437 lastLeaf = { 35438 consumed: i - start + 1, 35439 value: trienode.__leaf 35440 }; 35441 } 35442 if (array[i] === 0) { 35443 // null-terminator, so end the mapping. 35444 return { 35445 consumed: 1, 35446 value: 0 35447 }; 35448 } else if (typeof(trienode[array[i]]) !== 'undefined') { 35449 // we have a mapping 35450 if (isValue(trienode[array[i]])) { 35451 // it is a leaf node 35452 return { 35453 consumed: i - start + 1, 35454 value: trienode[array[i]] 35455 }; 35456 } else { 35457 // it is an intermediate node 35458 trienode = trienode[array[i++]]; 35459 } 35460 } else { 35461 // no mapping for this array element, so return the last known 35462 // leaf. If none, this will return undefined. 35463 return lastLeaf; 35464 } 35465 } 35466 35467 return undefined; 35468 }; 35469 35470 /** 35471 * Map a string to the native character set. This string may be 35472 * given as an intrinsic Javascript string object or an IString 35473 * object. 35474 * 35475 * @param {string|IString} string string to map to a different 35476 * character set. 35477 * @return {Uint8Array} An array of bytes representing the string 35478 * in the native character set 35479 */ 35480 CharmapTable.prototype.mapToNative = function(string) { 35481 if (!string) { 35482 return new Uint8Array(0); 35483 } 35484 35485 var str = (string instanceof IString) ? string : new IString(string); 35486 35487 // use IString's iterator so that we take care of walking through 35488 // the code points correctly, including the surrogate pairs 35489 // var c, i = 0, it = str.charIterator(); 35490 var ret = new Uint8Array(str.length * this.expansionFactor); 35491 35492 var i = 0, j = 0; 35493 35494 while (i < string.length) { 35495 var result = this._trieWalk(this.map.from, string, i); 35496 if (result) { 35497 if (result.value) { 35498 i += result.consumed; 35499 j += this.writeNative(ret, j, result.value); 35500 } else { 35501 // null-termination 35502 i = string.length; 35503 this.writeNative(ret, j, [result.value]); 35504 } 35505 } else { 35506 // The unicode char at "i" didn't have any mapping, so 35507 // deal with the missing char 35508 j += this.writeNativeString(ret, j, this.dealWithMissingChar(string[i++])); 35509 } 35510 } 35511 35512 return ret.subarray(0, j); 35513 }; 35514 35515 /** 35516 * Map a native string to the standard Javascript charset of UTF-16. 35517 * This string may be given as an array of numbers where each number 35518 * represents a code point in the "from" charset, or as a Uint8Array 35519 * array of bytes representing the bytes of the string in order. 35520 * 35521 * @param {Array.<number>|Uint8Array} bytes bytes to map to 35522 * a Unicode string 35523 * @return {string} A string in the standard Javascript charset UTF-16 35524 */ 35525 CharmapTable.prototype.mapToUnicode = function(bytes) { 35526 var ret = ""; 35527 var i = 0; 35528 35529 while (i < bytes.length) { 35530 var result = this._trieWalk(this.map.to, bytes, i); 35531 if (result) { 35532 if (result.value) { 35533 i += result.consumed; 35534 if (typeof(result.value) === 'string') { 35535 ret += result.value; 35536 } else if (ilib.isArray(result.value)) { 35537 for (var j = 0; j < result.value.length; j++) { 35538 ret += result.value[j]; 35539 } 35540 } // else error in charmap file?? 35541 } else { 35542 // null-termination 35543 i = bytes.length; 35544 } 35545 } else { 35546 // The byte at "i" wasn't a lead byte, so start again at the 35547 // next byte instead. This may synchronize the rest 35548 // of the string. 35549 ret += this.dealWithMissingChar(bytes[i++]); 35550 } 35551 } 35552 35553 return ret; 35554 }; 35555 35556 Charmap._algorithms["CharmapTable"] = CharmapTable; 35557 35558 35559 /*< CharmapFactory.js */ 35560 /* 35561 * CharmapFactory.js - Factory class to create the right subclasses of a charmap for any 35562 * given chararacter set. 35563 * 35564 * Copyright © 2015, JEDLSoft 35565 * 35566 * Licensed under the Apache License, Version 2.0 (the "License"); 35567 * you may not use this file except in compliance with the License. 35568 * You may obtain a copy of the License at 35569 * 35570 * http://www.apache.org/licenses/LICENSE-2.0 35571 * 35572 * Unless required by applicable law or agreed to in writing, software 35573 * distributed under the License is distributed on an "AS IS" BASIS, 35574 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35575 * 35576 * See the License for the specific language governing permissions and 35577 * limitations under the License. 35578 */ 35579 35580 /* !depends ilib.js JSUtils.js Charmap.js CharmapTable.js */ 35581 // !data charset/ISO-8859-15 charmaps/ISO-8859-15 35582 35583 35584 35585 /** 35586 * Factory method to create a new instance of a character set mapping (charmap) 35587 * subclass that is appropriate for the requested charset. Charmap instances map strings to 35588 * other character sets. The charsets can be of any type, single-byte, multi-byte, 35589 * shifting, etc. <p> 35590 * 35591 * All mappings are done to or from Unicode in the UTF-16 encoding, which is the base 35592 * character set and encoding used by Javascript itself. In order to convert 35593 * between two non-Unicode character sets, you must chain two charmap instances together 35594 * to first map to Unicode and then back to the second charset. <p> 35595 * 35596 * The options parameter controls which mapping is constructed and its behaviours. The 35597 * current list of supported options are: 35598 * 35599 * <ul> 35600 * <li><i>name</i> - the name of the native charset to map to or from. This can be 35601 * given as an {@link Charset} instance or as a string that contains any commonly used name 35602 * for the character set, which is normalized to a standard IANA name. 35603 * If a name is not given, this class will default to the Western European character 35604 * set called ISO-8859-15. 35605 * 35606 * <li><i>missing</i> - specify what to do if a mapping is missing for a particular 35607 * character. For example, if you are mapping Unicode characters to a particular native 35608 * character set that does not support particular Unicode characters, the mapper will 35609 * follow the behaviour specified in this property. Valid values are: 35610 * <ul> 35611 * <li><i>skip</i> - skip any characters that do not exist in the target charset 35612 * <li><i>placeholder</i> - put a static placeholder character in the output string 35613 * wherever there is an unknown character in the input string. Use the <i>placeholder</i> 35614 * parameter to specify which character to use in this case 35615 * <li><i>escape</i> - use an escape sequence to represent the unknown character 35616 * </ul> 35617 * The default value for the missing property if not otherwise specified is "escape" 35618 * so that information is not lost. 35619 * 35620 * <li><i>placeholder</i> - specify the placeholder character to use when the 35621 * mapper cannot map a particular input character to the output string. If this 35622 * option is not specified, then the '?' (question mark) character is used where 35623 * possible. 35624 * 35625 * <li><i>escapeStyle</i> - what style of escape sequences should be used to 35626 * escape unknown characters in the input when mapping to native, and what 35627 * style of espcae sequences should be parsed when mapping to Unicode. Valid 35628 * values are: 35629 * <ul> 35630 * <li><i>html</i> - Escape the characters as HTML entities. This would use 35631 * the standard HTML 5.0 (or later) entity names where possible, and numeric 35632 * entities in all other cases. Eg. an "e" with an acute accent would be 35633 * "é" 35634 * <li><i>js</i> - Use the Javascript escape style. Eg. an "e" with an acute 35635 * accent would be "\u00E9". This can also be specified as "c#" as 35636 * it uses a similar escape syntax. 35637 * <li><i>c</i> - Use the C/C++ escape style, which is similar to the the 35638 * Javascript style, but uses an "x" in place of the "u". Eg. an "e" with an 35639 * acute accent would be "\x00E9". This can also be specified as "c++". 35640 * <li><i>java</i> - Use the Java escape style. This is very similar to the 35641 * the Javascript style, but the backslash has to be escaped twice. Eg. an 35642 * "e" with an acute accent would be "\\u00E9". This can also be specified 35643 * as "ruby", as Ruby uses a similar escape syntax with double backslashes. 35644 * <li><i>perl</i> - Use the Perl escape style. Eg. an "e" with an acute 35645 * accent would be "\N{U+00E9}" 35646 * </ul> 35647 * The default if this style is not specified is "js" for Javascript. 35648 * 35649 * <li><i>onLoad</i> - a callback function to call when this object is fully 35650 * loaded. When the onLoad option is given, this class will attempt to 35651 * load any missing data using the ilib loader callback. 35652 * When the constructor is done (even if the data is already preassembled), the 35653 * onLoad function is called with the current instance as a parameter, so this 35654 * callback can be used with preassembled or dynamic loading or a mix of the two. 35655 * 35656 * <li><i>sync</i> - tell whether to load any missing data synchronously or 35657 * asynchronously. If this option is given as "false", then the "onLoad" 35658 * callback must be given, because the instance returned from this constructor will 35659 * not be usable for a while. 35660 * 35661 * <li><i>loadParams</i> - an object containing parameters to pass to the 35662 * loader callback function when data is missing. The parameters are not 35663 * interpretted or modified in any way. They are simply passed along. The object 35664 * may contain any property/value pairs as long as the calling code is in 35665 * agreement with the loader callback function as to what those parameters mean. 35666 * </ul> 35667 * 35668 * If this copy of ilib is pre-assembled and all the data is already available, 35669 * or if the data was already previously loaded, then this constructor will call 35670 * the onLoad callback immediately when the initialization is done. 35671 * If the onLoad option is not given, this class will only attempt to load any 35672 * missing data synchronously. 35673 * 35674 * @static 35675 * @param {Object=} options options controlling the construction of this instance, or 35676 * undefined to use the default options 35677 * @return {Charmap|undefined} an instance of a character set mapping class appropriate for 35678 * the requested charset, or undefined if no mapper could be found that supports the 35679 * requested charset 35680 */ 35681 var CharmapFactory = function(options) { 35682 var charsetName = (options && options.name) || "ISO-8859-15"; 35683 var sync = true; 35684 35685 // console.log("CharmapFactory: called with options: " + JSON.stringify(options)); 35686 35687 if (options) { 35688 if (typeof(options.sync) === 'boolean') { 35689 sync = options.sync; 35690 } 35691 } else { 35692 options = {sync: true}; 35693 } 35694 35695 var instance; 35696 35697 new Charset({ 35698 name: charsetName, 35699 sync: sync, 35700 loadParams: options.loadParams, 35701 onLoad: function (charset) { 35702 // name will be normalized already 35703 var cons, name = charset.getName(); 35704 35705 // console.log("CharmapFactory: normalized charset name: " + name); 35706 35707 if (!Charmap._algorithms[name] && ilib.isDynCode()) { 35708 // console.log("CharmapFactory: isDynCode. Doing require"); 35709 var entry = CharmapFactory._dynMap[name] || "CharmapTable"; 35710 cons = Charmap._algorithms[name] = require("./" + entry + ".js"); 35711 } 35712 35713 if (!cons) { 35714 cons = Charmap._algorithms[name] || Charmap._algorithms["CharmapTable"]; 35715 } 35716 35717 // console.log("CharmapFactory: cons is "); console.dir(cons); 35718 35719 // pass the same options through to the constructor so the subclass 35720 // has the ability to do something with if it needs to 35721 instance = cons && new cons(JSUtils.merge(options || {}, {charset: charset})); 35722 } 35723 }); 35724 35725 return instance; 35726 }; 35727 35728 35729 /** 35730 * Map standardized charset names to classes to initialize in the dynamic code model. 35731 * These classes implement algorithmic mappings instead of table-based ones. 35732 * TODO: Need to figure out some way that this doesn't have to be updated by hand. 35733 * @private 35734 */ 35735 CharmapFactory._dynMap = { 35736 "UTF-8": "UTF8", 35737 "UTF-16": "UTF16LE", 35738 "UTF-16LE": "UTF16LE", 35739 "UTF-16BE": "UTF16BE", 35740 "US-ASCII": "Charmap" 35741 /* 35742 not implemented yet 35743 "ISO-2022-JP": "ISO2022", 35744 "ISO-2022-JP-1": "ISO2022", 35745 "ISO-2022-JP-2": "ISO2022", 35746 "ISO-2022-JP-3": "ISO2022", 35747 "ISO-2022-JP-2004": "ISO2022", 35748 "ISO-2022-CN": "ISO2022", 35749 "ISO-2022-CN-EXT": "ISO2022", 35750 "ISO-2022-KR": "ISO2022" 35751 */ 35752 }; 35753 35754 35755 /*< UTF8.js */ 35756 /* 35757 * UTF8.js - Implement Unicode Transformation Format 8-bit mappings 35758 * 35759 * Copyright © 2014-2015, 2018, JEDLSoft 35760 * 35761 * Licensed under the Apache License, Version 2.0 (the "License"); 35762 * you may not use this file except in compliance with the License. 35763 * You may obtain a copy of the License at 35764 * 35765 * http://www.apache.org/licenses/LICENSE-2.0 35766 * 35767 * Unless required by applicable law or agreed to in writing, software 35768 * distributed under the License is distributed on an "AS IS" BASIS, 35769 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35770 * 35771 * See the License for the specific language governing permissions and 35772 * limitations under the License. 35773 */ 35774 35775 // !depends Charmap.js IString.js 35776 35777 // !data charset/UTF-8 35778 35779 35780 /** 35781 * @class 35782 * Create a new UTF-8 mapping instance 35783 * @constructor 35784 * @extends Charmap 35785 */ 35786 var UTF8 = function (options) { 35787 options = options || {sync: true}; 35788 if (typeof(options.charset) === "object" && options.charset instanceof Charset) { 35789 this.charset = options.charset; 35790 this._init(options); 35791 } else { 35792 new Charset({ 35793 name: "UTF-8", 35794 sync: options.sync, 35795 loadParams: options.loadParams, 35796 onLoad: ilib.bind(this, function(cs) { 35797 this.charset = cs; 35798 this._init(options); 35799 }) 35800 }); 35801 } 35802 }; 35803 35804 UTF8.prototype = new Charmap({noinstance: true}); 35805 UTF8.prototype.parent = Charmap; 35806 UTF8.prototype.constructor = UTF8; 35807 35808 /** 35809 * @private 35810 * Initialize the charmap instance 35811 */ 35812 UTF8.prototype._init = function(options) { 35813 this._calcExpansionFactor(); 35814 35815 if (typeof(options.onLoad) === "function") { 35816 options.onLoad(this); 35817 } 35818 }; 35819 35820 UTF8.prototype.validate = function(bytes) { 35821 var i = 0; 35822 while (i < bytes.length) { 35823 if ((bytes[i] & 0x80) === 0) { 35824 i++; 35825 } else { 35826 var len; 35827 if ((bytes[i] & 0xC0) === 0xC0) { 35828 len = 2; 35829 } else if ((bytes[i] & 0xE0) === 0xE0) { 35830 len = 3; 35831 } else if ((bytes[i] & 0xF0) === 0xF0) { 35832 len = 4; 35833 } else { 35834 // invalid lead byte 35835 return false; 35836 } 35837 if (i + len > bytes.length) { 35838 // not enough trailing bytes 35839 return false; 35840 } 35841 for (var j = 1; j < len; j++) { 35842 // check each trailing byte to see if it has the correct form 35843 if ((bytes[i+j] & 0x80) !== 0x80) { 35844 return false; 35845 } 35846 } 35847 i += len; 35848 } 35849 } 35850 35851 return true; 35852 }; 35853 35854 UTF8.prototype.mapToUnicode = function (bytes) { 35855 if (typeof(Buffer) !== "undefined") { 35856 // nodejs can convert it quickly in native code 35857 var b = new Buffer(bytes); 35858 return b.toString("utf8"); 35859 } 35860 // otherwise we have to implement it in pure JS 35861 var ret = ""; 35862 var i = 0; 35863 while (i < bytes.length) { 35864 if (bytes[i] === 0) { 35865 // null-terminator 35866 i = bytes.length; 35867 } else if ((bytes[i] & 0x80) === 0) { 35868 // 1 byte char 35869 ret += String.fromCharCode(bytes[i++]); 35870 } else if ((bytes[i] & 0xE0) === 0xC0) { 35871 // 2 byte char 35872 if (i + 1 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80) { 35873 throw "invalid utf-8 bytes"; 35874 } 35875 // xxx xxyyyyyy 35876 ret += String.fromCharCode((bytes[i] & 0x1F) << 6 | (bytes[i+1] & 0x3F)); 35877 i += 2; 35878 } else if ((bytes[i] & 0xF0) === 0xE0) { 35879 // 3 byte char 35880 if (i + 2 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80) { 35881 throw "invalid utf-8 bytes"; 35882 } 35883 // xxxxyyyy yyzzzzzz 35884 ret += String.fromCharCode((bytes[i] & 0xF) << 12 | (bytes[i+1] & 0x3F) << 6 | (bytes[i+2] & 0x3F)); 35885 i += 3; 35886 } else if ((bytes[i] & 0xF8) === 0xF0) { 35887 // 4 byte char 35888 if (i + 3 >= bytes.length || (bytes[i+1] & 0x80) !== 0x80 || (bytes[i+2] & 0x80) !== 0x80 || (bytes[i+3] & 0x80) !== 0x80) { 35889 throw "invalid utf-8 bytes"; 35890 } 35891 // wwwxx xxxxyyyy yyzzzzzz 35892 ret += IString.fromCodePoint((bytes[i] & 0x7) << 18 | (bytes[i+1] & 0x3F) << 12 | (bytes[i+2] & 0x3F) << 6 | (bytes[i+3] & 0x3F)); 35893 i += 4; 35894 } else { 35895 throw "invalid utf-8 bytes"; 35896 } 35897 } 35898 35899 return ret; 35900 }; 35901 35902 UTF8.prototype.mapToNative = function(str) { 35903 if (typeof(Buffer) !== "undefined") { 35904 // nodejs can convert it quickly in native code 35905 var b = new Buffer(str, "utf8"); 35906 return new Uint8Array(b); 35907 } 35908 // otherwise we have to implement it in pure JS 35909 var istr = (str instanceof IString) ? str : new IString(str); 35910 35911 // step through the surrogate pairs as single code points by using 35912 // IString's iterator 35913 var it = istr.iterator(); 35914 35915 // multiply by 4 because the max size of a UTF-8 char is 4 bytes, so 35916 // this will at least get us enough room to encode everything. Add 1 35917 // for the null terminator 35918 var ret = new Uint8Array(istr.length * 4 + 1); 35919 var i = 0; 35920 35921 while (it.hasNext()) { 35922 var c = it.next(); 35923 if (c > 0x7F) { 35924 if (c > 0x7FF) { 35925 if (c > 0xFFFF) { 35926 // astral planes char 35927 ret[i] = 0xF0 | ((c >> 18) & 0x3); 35928 ret[i+1] = 0x80 | ((c >> 12) & 0x3F); 35929 ret[i+2] = 0x80 | ((c >> 6) & 0x3F); 35930 ret[i+3] = 0x80 | (c & 0x3F); 35931 35932 i += 4; 35933 } else { 35934 ret[i] = 0xE0 | ((c >> 12) & 0xF); 35935 ret[i+1] = 0x80 | ((c >> 6) & 0x3F); 35936 ret[i+2] = 0x80 | (c & 0x3F); 35937 35938 i += 3; 35939 } 35940 } else { 35941 ret[i] = 0xC0 | ((c >> 6) & 0x1F); 35942 ret[i+1] = 0x80 | (c & 0x3F); 35943 35944 i += 2; 35945 } 35946 } else { 35947 ret[i++] = (c & 0x7F); 35948 } 35949 } 35950 ret[i] = 0; // null-terminate it 35951 35952 return ret; 35953 }; 35954 35955 Charmap._algorithms["UTF-8"] = UTF8; 35956 35957 35958 /*< UTF16BE.js */ 35959 /* 35960 * UTF16BE.js - Implement Unicode Transformation Format 16-bit, 35961 * Big Endian mappings 35962 * 35963 * Copyright © 2014-2015, 2018, JEDLSoft 35964 * 35965 * Licensed under the Apache License, Version 2.0 (the "License"); 35966 * you may not use this file except in compliance with the License. 35967 * You may obtain a copy of the License at 35968 * 35969 * http://www.apache.org/licenses/LICENSE-2.0 35970 * 35971 * Unless required by applicable law or agreed to in writing, software 35972 * distributed under the License is distributed on an "AS IS" BASIS, 35973 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 35974 * 35975 * See the License for the specific language governing permissions and 35976 * limitations under the License. 35977 */ 35978 35979 // !depends Charmap.js 35980 35981 // !data charset/UTF-16 charset/UTF-16BE 35982 35983 35984 /** 35985 * @class 35986 * Create a new UTF-16BE mapping instance 35987 * @constructor 35988 * @extends Charmap 35989 */ 35990 var UTF16BE = function (options) { 35991 options = options || {sync: true}; 35992 if (typeof(options.charset) === "object" && options.charset instanceof Charset) { 35993 this.charset = options.charset; 35994 this._init(options); 35995 } else { 35996 new Charset({ 35997 name: "UTF-16BE", 35998 sync: options.sync, 35999 loadParams: options.loadParams, 36000 onLoad: ilib.bind(this, function(cs) { 36001 this.charset = cs; 36002 this._init(options); 36003 }) 36004 }); 36005 } 36006 }; 36007 36008 UTF16BE.prototype = new Charmap({noinstance: true}); 36009 UTF16BE.prototype.parent = Charmap; 36010 UTF16BE.prototype.constructor = UTF16BE; 36011 36012 /** 36013 * @private 36014 * Initialize the charmap instance 36015 */ 36016 UTF16BE.prototype._init = function(options) { 36017 this._calcExpansionFactor(); 36018 36019 if (typeof(options.onLoad) === "function") { 36020 options.onLoad(this); 36021 } 36022 }; 36023 36024 UTF16BE.prototype.mapToUnicode = function (bytes) { 36025 // nodejs can't convert big-endian in native code, 36026 // so we would have to flip each Uint16 ourselves. 36027 // At that point, it's just quicker to convert 36028 // in JS code anyways 36029 var ret = ""; 36030 for (var i = 0; i < bytes.length; i += 2) { 36031 ret += String.fromCharCode(bytes[i] << 8 | bytes[i+1]); 36032 } 36033 36034 return ret; 36035 }; 36036 36037 UTF16BE.prototype.mapToNative = function(str) { 36038 // nodejs can't convert big-endian in native code, 36039 // so we would have to flip each Uint16 ourselves. 36040 // At that point, it's just quicker to convert 36041 // in JS code anyways 36042 var ret = new Uint8Array(str.length * 2 + 2); 36043 var c; 36044 for (var i = 0; i < str.length; i++) { 36045 c = str.charCodeAt(i); 36046 ret[i*2] = (c >> 8) & 0xFF; 36047 ret[i*2+1] = c & 0xFF; 36048 } 36049 // double null terminate it, just in case 36050 ret[i*2+1] = 0; 36051 ret[i*2+2] = 0; 36052 36053 return ret; 36054 }; 36055 36056 Charmap._algorithms["UTF-16BE"] = UTF16BE; 36057 36058 36059 /*< UTF16LE.js */ 36060 /* 36061 * UTF16LE.js - Implement Unicode Transformation Format 16 bit, 36062 * Little Endian mappings 36063 * 36064 * Copyright © 2014-2015, 2018, JEDLSoft 36065 * 36066 * Licensed under the Apache License, Version 2.0 (the "License"); 36067 * you may not use this file except in compliance with the License. 36068 * You may obtain a copy of the License at 36069 * 36070 * http://www.apache.org/licenses/LICENSE-2.0 36071 * 36072 * Unless required by applicable law or agreed to in writing, software 36073 * distributed under the License is distributed on an "AS IS" BASIS, 36074 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36075 * 36076 * See the License for the specific language governing permissions and 36077 * limitations under the License. 36078 */ 36079 36080 // !depends Charmap.js 36081 36082 // !data charset/UTF-16 charset/UTF-16LE 36083 36084 36085 /** 36086 * @class 36087 * Create a new UTF-16LE mapping instance 36088 * @constructor 36089 * @extends Charmap 36090 */ 36091 var UTF16LE = function (options) { 36092 options = options || {sync: true}; 36093 if (typeof(options.charset) === "object" && options.charset instanceof Charset) { 36094 this.charset = options.charset; 36095 this._init(options); 36096 } else { 36097 new Charset({ 36098 name: "UTF-16LE", 36099 sync: options.sync, 36100 loadParams: options.loadParams, 36101 onLoad: ilib.bind(this, function(cs) { 36102 this.charset = cs; 36103 this._init(options); 36104 }) 36105 }); 36106 } 36107 }; 36108 36109 UTF16LE.prototype = new Charmap({noinstance: true}); 36110 UTF16LE.prototype.parent = Charmap; 36111 UTF16LE.prototype.constructor = UTF16LE; 36112 36113 /** 36114 * @private 36115 * Initialize the charmap instance 36116 */ 36117 UTF16LE.prototype._init = function(options) { 36118 this._calcExpansionFactor(); 36119 36120 if (typeof(options.onLoad) === "function") { 36121 options.onLoad(this); 36122 } 36123 }; 36124 36125 UTF16LE.prototype.mapToUnicode = function (bytes) { 36126 if (typeof(Buffer) !== "undefined") { 36127 // nodejs can convert it quickly in native code 36128 var b = new Buffer(bytes); 36129 return b.toString("utf16le"); 36130 } 36131 // otherwise we have to implement it in pure JS 36132 var ret = ""; 36133 for (var i = 0; i < bytes.length; i += 2) { 36134 ret += String.fromCharCode(bytes[i+1] << 8 | bytes[i]); 36135 } 36136 36137 return ret; 36138 }; 36139 36140 UTF16LE.prototype.mapToNative = function(str) { 36141 if (typeof(Buffer) !== "undefined") { 36142 // nodejs can convert it quickly in native code 36143 var b = new Buffer(str, "utf16le"); 36144 return new Uint8Array(b); 36145 } 36146 // otherwise we have to implement it in pure JS 36147 var ret = new Uint8Array(str.length * 2 + 2); 36148 var c; 36149 for (var i = 0; i < str.length; i++) { 36150 c = str.charCodeAt(i); 36151 ret[i*2] = c & 0xFF; 36152 ret[i*2+1] = (c >> 8) & 0xFF; 36153 } 36154 // double null terminate it, just in case 36155 ret[i*2+1] = 0; 36156 ret[i*2+2] = 0; 36157 36158 return ret; 36159 }; 36160 36161 Charmap._algorithms["UTF-16"] = UTF16LE; 36162 Charmap._algorithms["UTF-16LE"] = UTF16LE; 36163 36164 36165 /*< Country.js */ 36166 /* 36167 * Country.js - Country class to get country name corresponding to country code in locale assigned 36168 * 36169 * Copyright © 2017, LGE 36170 * 36171 * Licensed under the Apache License, Version 2.0 (the "License"); 36172 * you may not use this file except in compliance with the License. 36173 * You may obtain a copy of the License at 36174 * 36175 * http://www.apache.org/licenses/LICENSE-2.0 36176 * 36177 * Unless required by applicable law or agreed to in writing, software 36178 * distributed under the License is distributed on an "AS IS" BASIS, 36179 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36180 * 36181 * See the License for the specific language governing permissions and 36182 * limitations under the License. 36183 */ 36184 36185 // !depends ilib.js Utils.js Locale.js LocaleInfo.js ResBundle.js 36186 36187 // !data ctryreverse 36188 36189 36190 /** 36191 * @class 36192 * Create a new country information instance. Instances of this class encode 36193 * information about country name.<p> 36194 * 36195 * The options can contain any of the following properties: 36196 * 36197 * <ul> 36198 * <li><i>locale</i> - specify the locale for this instance. Country names are provided 36199 * in the language of this locale. 36200 * 36201 * <li><i>onLoad</i> - a callback function to call when the country name data is fully 36202 * loaded. When the onLoad option is given, this class will attempt to 36203 * load any missing locale data using the ilib loader callback. 36204 * When the constructor is done (even if the data is already preassembled), the 36205 * onLoad function is called with the current instance as a parameter, so this 36206 * callback can be used with preassembled or dynamic loading or a mix of the two. 36207 * 36208 * <li><i>sync</i> - tell whether to load any missing locale data synchronously or 36209 * asynchronously. If this option is given as "false", then the "onLoad" 36210 * callback must be given, as the instance returned from this constructor will 36211 * not be usable for a while. 36212 * 36213 * <li><i>loadParams</i> - an object containing parameters to pass to the 36214 * loader callback function when locale data is missing. The parameters are not 36215 * interpretted or modified in any way. They are simply passed along. The object 36216 * may contain any property/value pairs as long as the calling code is in 36217 * agreement with the loader callback function as to what those parameters mean. 36218 * </ul> 36219 * 36220 * If the locale is not set, the default locale(en-US) will be used.<p> 36221 * 36222 * @constructor 36223 * @param options {Object} a set of properties to govern how this instance is constructed. 36224 */ 36225 var Country = function (options) { 36226 var sync = true, 36227 loadParams = undefined, 36228 locale; 36229 36230 if (options) { 36231 if (options.locale) { 36232 this.locale = (typeof(options.locale) === 'string') ? new Locale(options.locale) : options.locale; 36233 } 36234 if (typeof(options.sync) !== 'undefined') { 36235 sync = !!options.sync; 36236 } 36237 if (options.loadParams) { 36238 loadParams = options.loadParams; 36239 } 36240 } 36241 36242 this.locale = this.locale || new Locale(); 36243 new LocaleInfo(this.locale, { 36244 sync: sync, 36245 loadParams: loadParams, 36246 onLoad: ilib.bind(this, function (li) { 36247 this.locinfo = li; 36248 if (this.locinfo.getRegionName() === undefined) { 36249 locale = 'en-US'; 36250 } else { 36251 locale = this.locale; 36252 } 36253 36254 if (!this.codeToCountry) { 36255 Utils.loadData({ 36256 name: "ctryreverse.json", 36257 object: Country, 36258 locale: locale, 36259 sync: sync, 36260 loadParams: loadParams, 36261 callback: ilib.bind(this, function(countries) { 36262 this.codeToCountry = countries; 36263 this._calculateCountryToCode(); 36264 if (options && typeof(options.onLoad) === 'function') { 36265 options.onLoad(this); 36266 } 36267 }) 36268 }); 36269 } else { 36270 this._calculateCountryToCode(); 36271 if (options && typeof(options.onLoad) === 'function') { 36272 options.onLoad(this); 36273 } 36274 } 36275 }) 36276 }); 36277 }; 36278 36279 /** 36280 * Return an array of the ids for all ISO 3166-1 alpha-2 code that 36281 * this copy of ilib knows about. 36282 * 36283 * @static 36284 * @return {Object} an object of country code that this copy of ilib knows about. 36285 */ 36286 Country.getAvailableCode = function() { 36287 var countries = new ResBundle({ 36288 name: "ctryreverse" 36289 }).getResObj(); 36290 36291 return countries && Object.keys(countries); 36292 }; 36293 36294 /** 36295 * Return an array of country names that this copy of ilib knows about. 36296 * 36297 * @static 36298 * @return {Object} an object of country code that this copy of ilib knows about. 36299 */ 36300 Country.getAvailableCountry = function() { 36301 var ret = [], 36302 code, 36303 countries = new ResBundle({ 36304 name: "ctryreverse" 36305 }).getResObj(); 36306 36307 for (code in countries) { 36308 if (code && countries[code]) { 36309 ret.push(countries[code]); 36310 } 36311 } 36312 36313 return ret; 36314 }; 36315 36316 Country.prototype = { 36317 /** 36318 * @private 36319 */ 36320 _calculateCountryToCode: function() { 36321 var temp = this.codeToCountry, 36322 code; 36323 36324 this.countryToCode = {}; 36325 36326 for (code in temp) { 36327 if (code && temp[code]) { 36328 this.countryToCode[temp[code]] = code; 36329 } 36330 } 36331 }, 36332 36333 /** 36334 * Return the country code corresponding to the country name given. 36335 * If the country name is given, but it is not found in the list of known countries, this 36336 * method will throw an exception. 36337 * @param {string} ctryname The country name in the language of the locale of this instance 36338 * @return {string} the country code corresponding to the country name 36339 * @throws "Country xx is unknown" when the given country name is not in the list of 36340 * known country names. xx is replaced with the requested country name. 36341 */ 36342 getCode: function (ctryname) { 36343 if (!this.countryToCode[ctryname]) { 36344 throw "Country " + ctryname + " is unknown"; 36345 } 36346 return this.countryToCode[ctryname]; 36347 }, 36348 36349 /** 36350 * Return the country name corresponding to the country code given. 36351 * If the code is given, but it is not found in the list of known countries, this 36352 * method will throw an exception. 36353 * @param {string} code The country code to get the country name 36354 * @return {string} the country name in the language of the locale of this instance 36355 * @throws "Country xx is unknown" when the given country code is not in the list of 36356 * known country codes. xx is replaced with the requested country code. 36357 */ 36358 getName: function (code) { 36359 if (!this.codeToCountry[code]) { 36360 throw "Country " + code + " is unknown"; 36361 } 36362 return this.codeToCountry[code]; 36363 }, 36364 36365 /** 36366 * Return the locale for this country. If the options to the constructor 36367 * included a locale property in order to find the country that is appropriate 36368 * for that locale, then the locale is returned here. If the options did not 36369 * include a locale, then this method returns undefined. 36370 * @return {Locale} the locale used in the constructor of this instance, 36371 * or undefined if no locale was given in the constructor 36372 */ 36373 getLocale: function () { 36374 return this.locale; 36375 } 36376 }; 36377 36378 36379 36380 /*< /data/root/home/edwin/src/ilib/js/lib/ilib-full-inc.js */ 36381 /** 36382 * @license 36383 * Copyright © 2012-2015, JEDLSoft 36384 * 36385 * Licensed under the Apache License, Version 2.0 (the "License"); 36386 * you may not use this file except in compliance with the License. 36387 * You may obtain a copy of the License at 36388 * 36389 * http://www.apache.org/licenses/LICENSE-2.0 36390 * 36391 * Unless required by applicable law or agreed to in writing, software 36392 * distributed under the License is distributed on an "AS IS" BASIS, 36393 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 36394 * 36395 * See the License for the specific language governing permissions and 36396 * limitations under the License. 36397 */ 36398 36399 /* 36400 * ilib-full-inc.js - metafile that includes all other js files 36401 */ 36402 36403 /* !depends 36404 ilib.js 36405 DateRngFmt.js 36406 IDate.js 36407 DateFactory.js 36408 HebrewDate.js 36409 HebrewCal.js 36410 IslamicCal.js 36411 IslamicDate.js 36412 JulianCal.js 36413 JulianDate.js 36414 GregorianCal.js 36415 GregorianDate.js 36416 ThaiSolarCal.js 36417 ThaiSolarDate.js 36418 PersianCal.js 36419 PersianDate.js 36420 PersianAlgoCal.js 36421 PersianAlgoDate.js 36422 HanCal.js 36423 HanDate.js 36424 EthiopicCal.js 36425 EthiopicDate.js 36426 CopticCal.js 36427 CopticDate.js 36428 INumber.js 36429 NumFmt.js 36430 JulianDay.js 36431 DateFmt.js 36432 Calendar.js 36433 CalendarFactory.js 36434 Utils.js 36435 Locale.js 36436 IString.js 36437 DurationFmt.js 36438 ResBundle.js 36439 CType.js 36440 LocaleInfo.js 36441 DateRngFmt.js 36442 isAlnum.js 36443 isAlpha.js 36444 isAscii.js 36445 isBlank.js 36446 isCntrl.js 36447 isDigit.js 36448 isGraph.js 36449 isIdeo.js 36450 isLower.js 36451 isPrint.js 36452 isPunct.js 36453 isSpace.js 36454 isUpper.js 36455 isXdigit.js 36456 isScript.js 36457 ScriptInfo.js 36458 Name.js 36459 NameFmt.js 36460 Address.js 36461 AddressFmt.js 36462 Collator.js 36463 nfkc/all.js 36464 LocaleMatcher.js 36465 NormString.js 36466 CaseMapper.js 36467 GlyphString.js 36468 PhoneFmt.js 36469 PhoneGeoLocator.js 36470 PhoneNumber.js 36471 Measurement.js 36472 MeasurementFactory.js 36473 UnitFmt.js 36474 LengthUnit.js 36475 VelocityUnit.js 36476 DigitalStorageUnit.js 36477 TemperatureUnit.js 36478 UnknownUnit.js 36479 TimeUnit.js 36480 MassUnit.js 36481 AreaUnit.js 36482 FuelConsumptionUnit.js 36483 VolumeUnit.js 36484 EnergyUnit.js 36485 Charset.js 36486 Charmap.js 36487 CharmapFactory.js 36488 CharmapTable.js 36489 UTF8.js 36490 UTF16BE.js 36491 UTF16LE.js 36492 Country.js 36493 ListFmt.js 36494 DigitalSpeedUnit.js 36495 */ 36496 36497